หนึ่งในฟีเจอร์ที่ทรงพลังที่สุดของ 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!