Macros
ไม่มีคู่เทียบตรงๆ ใน TypeScript — แต่ decorators ใกล้เคียงที่สุด
หัวข้อที่มีชื่อว่า “ไม่มีคู่เทียบตรงๆ ใน TypeScript — แต่ decorators ใกล้เคียงที่สุด”Rust macros ไม่มีคู่เทียบเดียวใน TypeScript คู่เทียบที่ใกล้เคียงที่สุด:
- TypeScript decorators (
@Injectable(),@Column()) — คล้าย#[derive(...)]ตรงที่ใช้ annotate code แล้ว trigger behaviour ที่ generate ขึ้นมา - Template literal types / type-level metaprogramming — เหมือนกับ
macro_rules!ทำงานบน syntax ก่อน runtime - Babel / esbuild plugins — transformations ที่ apply ตอน build time
ความแตกต่างหลัก: Rust macro เป็น hygienic และทำงานบน AST เป็นส่วนหนึ่งของตัวภาษาเอง ไม่ใช่ระบบ annotation ที่แปะเพิ่มทีหลัง แถมทำงานตอน compile time โดยเข้าถึง token tree ของ compiler ได้เต็มที่
Macros สองประเภท
หัวข้อที่มีชื่อว่า “Macros สองประเภท”| ประเภท | Syntax | ทำอะไร |
|---|---|---|
Declarative (macro_rules!) | macro_rules! my_macro { ... } | Pattern-match บน token trees และ generate code |
Procedural (#[derive], #[attribute], function-like) | #[derive(Debug, Clone)] | รับ AST node และ emit AST nodes ใหม่ |
บทเรียนนี้เน้น macro_rules! (ประเภทที่ง่ายกว่า) และ derive macros (ซึ่งคุณได้ใช้แล้วโดยไม่รู้ตัว)
macro_rules! — declarative macros
หัวข้อที่มีชื่อว่า “macro_rules! — declarative macros”macro_rules! ทำงานเหมือน match expression สำหรับ syntax แต่ละ arm กำหนด pattern ของ tokens ที่จะ match และ template ของ tokens ที่จะ emit
// TypeScript — no direct equivalent.// Closest: a generic utility function or a build-time code generator.function repeat<T>(value: T, times: number): T[] { return Array.from({ length: times }, () => value);}
console.log(repeat("hi", 3)); // ["hi", "hi", "hi"]
// TypeScript decorators (stage 3) annotate classes at runtime:// @Injectable() class MyService { ... }// macro_rules! — match on syntax, emit codemacro_rules! say_hello { // arm 1: no arguments () => { println!("Hello!"); }; // arm 2: one expression argument ($name:expr) => { println!("Hello, {}!", $name); };}
// #[derive] macro — auto-implement traits at compile time#[derive(Debug, Clone, PartialEq)]struct Point { x: f64, y: f64,}
fn main() { say_hello!(); say_hello!("Rustacean");
let p1 = Point { x: 1.0, y: 2.0 }; let p2 = p1.clone(); // Clone derived println!("{:?}", p1); // Debug derived println!("p1 == p2: {}", p1 == p2); // PartialEq derived}vec![] — macro ที่คุณใช้ทุกวัน
หัวข้อที่มีชื่อว่า “vec![] — macro ที่คุณใช้ทุกวัน”vec![1, 2, 3] เป็น macro_rules! macro จาก standard library ตอน compile จะ expand เป็นโค้ดที่สร้าง Vec, เรียก push ทีละ element แล้ว return vector ออกมา ถ้าไม่มี macro ตัวนี้คุณต้องเขียนแบบนี้:
let mut v = Vec::new();v.push(1);v.push(2);v.push(3);println! และ format! ก็เป็น declarative macro เช่นกัน — parse format string ตอน compile time แล้ว generate โค้ด formatting ที่ type-safe ออกมา
Derive macros — ที่คุณใช้บ่อยที่สุด
หัวข้อที่มีชื่อว่า “Derive macros — ที่คุณใช้บ่อยที่สุด”ทุกครั้งที่คุณเขียน #[derive(Debug, Clone, Serialize, Deserialize)] คุณกำลัง invoke procedural macro สิ่งเหล่านี้มีพลังมากกว่า macro_rules! เพราะรับ AST ทั้งหมดของ struct หรือ enum และ emit code ใหม่ที่ต้องการ
| Derive | สิ่งที่ generate |
|---|---|
Debug | fmt::Debug impl — เปิดใช้ {:?} printing |
Clone | clone() method |
PartialEq / Eq | operators == และ != |
PartialOrd / Ord | <, >, <=, >= |
serde::Serialize / Deserialize | JSON (และ format อื่น) serialisation |
thiserror::Error | Display และ From impls สำหรับ error enums |
ลองเล่นดู
หัวข้อที่มีชื่อว่า “ลองเล่นดู”macro_rules! say_hello { () => { println!("Hello!"); }; ($name:expr) => { println!("Hello, {}!", $name); };}
#[derive(Debug, Clone, PartialEq)]struct Point { x: f64, y: f64,}
fn main() { say_hello!(); say_hello!("Rustacean"); let p1 = Point { x: 1.0, y: 2.0 }; let p2 = p1.clone(); println!("{:?}", p1); println!("p1 == p2: {}", p1 == p2);}Compiling…