หนึ่งในฟีเจอร์ที่ทรงพลังที่สุดของ Rust คือ macro system ที่ให้คุณสามารถ “เขียนโค้ดที่เขียนโค้ด” ได้ — ไม่ใช่แค่การแทนที่ข้อความธรรมดาแบบ C แต่สามารถสร้าง syntax ใหม่ สร้าง DSL (Domain Specific Language) และแม้แต่ปรับเปลี่ยน AST ได้โดยตรง!
บทความนี้จะพาคุณเจาะลึก macro ทั้งสองชนิดใน Rust — Declarative macro และ Procedural macro — พร้อมตัวอย่างการสร้าง DSL ที่ใช้ได้จริง
Declarative Macro (macro_rules!)
Declarative macro คือ macro แบบ pattern-matching ที่ใช้ macro_rules!
macro_rules! say_hello { () => { println!("Hello from macro!"); }; } fn main() { say_hello!(); // จะพิมพ์ข้อความ }
รับพารามิเตอร์
macro_rules! square { ($x:expr) => { $x * $x }; } fn main() { let n = square!(4 + 1); // กลายเป็น (4 + 1) * (4 + 1) println!("{}", n); // 25 }
ข้อดีคือ macro เหล่านี้ compile เร็ว ไม่ต้องใช้ crate แยก
สร้าง DSL เบื้องต้น
macro_rules! html { (<$tag:ident>$content:expr</$tag2:ident>) => { format!("<{0}>{1}</{0}>", stringify!($tag), $content) }; } fn main() { let page = html!(<h1>"ยินดีต้อนรับ"</h1>); println!("{}", page); // <h1>ยินดีต้อนรับ</h1> }
คุณสามารถสร้าง DSL สำหรับ HTML, SQL หรือ config ได้เองด้วย macro แบบนี้!

ข้อจำกัดของ macro_rules!
- ไม่สามารถเข้าถึง AST หรือ metadata ของ type ได้
- ซับซ้อนเมื่อมี pattern หลายรูปแบบ
- ไม่สามารถแปลงโค้ดให้มีผลตาม logic runtime ได้
Procedural Macro (แบบเทพ!)
Procedural macro คือโค้ด Rust ที่ compile-time เพื่อ generate โค้ดอื่นผ่านการจัดการ AST โดยใช้ proc_macro
crate
3 ประเภทของ procedural macro:
- Function-like macro:
my_macro!(...)
- Derive macro:
#[derive(MyTrait)]
- Attribute macro:
#[route]
,#[inline]
เป็นต้น
สร้าง Function-like Macro
// lib.rs use proc_macro::TokenStream; #[proc_macro] pub fn make_answer(_input: TokenStream) -> TokenStream { "fn answer() -> i32 { 42 }".parse().unwrap() }
// main.rs use my_macro_lib::make_answer; make_answer!(); fn main() { println!("{}", answer()); // 42 }
สร้าง DSL แบบ JSON Mini DSL
#[proc_macro] pub fn json(input: TokenStream) -> TokenStream { let s = input.to_string(); let json_string = format!("serde_json::json!({})", s); json_string.parse().unwrap() }
fn main() { let data = json!({ "name": "Rust", "safe": true, "features": ["macro", "ownership"] }); println!("{}", data["name"]); }
ใช้กับ #[derive]
#[derive(Debug, MyTrait)] struct Person { name: String, age: u32, }
Rust จะเรียก procedural macro เพื่อสร้าง method หรือ impl ให้ struct นี้ตอน compile
ข้อดีของ Macro System ใน Rust
- เขียน DSL ที่ดู clean และ readable
- สร้าง derive ได้เอง ทำให้โค้ดสั้นลง
- ใช้ macro ทำ code generation แทน boilerplate
ข้อเสีย
- debug ยาก ต้องเข้าใจ AST และ TokenStream
- error message จาก macro อ่านยากกว่า function ธรรมดา
คำแนะนำ
- เริ่มจาก macro_rules! และใช้จนคล่อง
- อ่าน AST ด้วย
cargo expand
เพื่อเข้าใจ macro output - ใช้
syn
+quote
ใน procedural macro จะช่วยให้โค้ดอ่านง่าย
บทสรุป
Macro system ของ Rust ทรงพลังและปลอดภัย ช่วยให้คุณเขียนโค้ดที่ maintain ได้ง่าย สร้าง abstraction และ DSL ที่เหมาะกับโดเมนของคุณโดยไม่สูญเสียประสิทธิภาพ
หากคุณเข้าใจ macro ของ Rust อย่างลึกซึ้ง — คุณจะกลายเป็น full-power Rustacean!