Skip to content

Metaprogramming

"Macros là code viết code." - Rust Book

1. Declarative Macros (macro_rules!)

Pipeline biên dịch Macro

Source Code                  Mở rộng Macro               Đã biên dịch
───────────                  ─────────────               ────────────
                             
vec![1, 2, 3]    ──────▶     {                  ──────▶    Binary
                               let mut v = Vec::new();
                               v.push(1);
                               v.push(2);
                               v.push(3);
                               v
                             }

Cú pháp Macro

rust
macro_rules! say_hello {
    // Pattern => Mở rộng
    () => {
        println!("Xin chào!");
    };
}

say_hello!();  // Mở rộng thành: println!("Xin chào!");

Pattern Matching trong Macros

rust
macro_rules! create_function {
    // Match identifier và type
    ($func_name:ident, $type:ty) => {
        fn $func_name(x: $type) -> $type {
            x * 2
        }
    };
}

create_function!(double_i32, i32);
create_function!(double_f64, f64);

// Mở rộng thành:
// fn double_i32(x: i32) -> i32 { x * 2 }
// fn double_f64(x: f64) -> f64 { x * 2 }

Fragment Specifiers

SpecifierKhớp vớiVí dụ
identIdentifierfoo, Vec
exprExpression1 + 2, func()
tyTypei32, Vec<String>
patPatternSome(x), _
stmtStatementlet x = 1;
blockBlock{ ... }
itemItemfn foo() {}
metaMeta itemcfg(test)
ttToken treeBất kỳ
literalLiteral1, "hello"

Patterns lặp

rust
macro_rules! vec_of_strings {
    // $(...),* nghĩa là "không hoặc nhiều, phân cách bởi dấu phẩy"
    ($($x:expr),*) => {
        {
            let mut v = Vec::new();
            $(
                v.push($x.to_string());
            )*
            v
        }
    };
}

let v = vec_of_strings!["a", "b", "c"];
// Mở rộng thành:
// {
//     let mut v = Vec::new();
//     v.push("a".to_string());
//     v.push("b".to_string());
//     v.push("c".to_string());
//     v
// }

Toán tử lặp

rust
// $(...)*  - Không hoặc nhiều
// $(...)+ - Một hoặc nhiều
// $(...)?  - Không hoặc một

macro_rules! match_all {
    // Dấu phẩy cuối tùy chọn
    ($($x:expr),+ $(,)?) => {
        // ...
    };
}

Macro Hygiene

rust
macro_rules! using_a {
    ($e:expr) => {
        {
            let a = 42;  // 'a' này là hygienic
            $e           // Không thể truy cập 'a' của macro
        }
    }
}

let a = 10;
let result = using_a!(a * 2);  // Dùng 'a' bên ngoài = 20, không phải 42

Ví dụ thực tế: HashMap Literal

rust
macro_rules! hashmap {
    ($($key:expr => $value:expr),* $(,)?) => {
        {
            let mut map = std::collections::HashMap::new();
            $(
                map.insert($key, $value);
            )*
            map
        }
    };
}

let scores = hashmap! {
    "Alice" => 100,
    "Bob" => 85,
    "Charlie" => 92,
};

2. Procedural Macros

Các loại Procedural Macros

                    Các loại Procedural Macro
┌──────────────────────────────────────────────────────────┐
│                                                          │
│   Kiểu Function      Derive              Attribute       │
│   ─────────────      ──────              ─────────       │
│                                                          │
│   my_macro!(...)     #[derive(MyTrait)]  #[my_attr]      │
│                                          fn foo() {}     │
│                                                          │
│   Input: TokenStream    Input: Struct    Input: Item     │
│   Output: TokenStream   Def only         + Attribute     │
│                         Output: Impl                     │
│                         TokenStream                      │
│                                                          │
└──────────────────────────────────────────────────────────┘

Thiết lập Project

toml
# Cargo.toml
[lib]
proc-macro = true

[dependencies]
quote = "1.0"
syn = { version = "2.0", features = ["full"] }
proc-macro2 = "1.0"

Ví dụ Derive Macro

rust
// Trong proc-macro crate: my_derive/src/lib.rs
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};

#[proc_macro_derive(HelloWorld)]
pub fn hello_world_derive(input: TokenStream) -> TokenStream {
    // Parse input tokens thành syntax tree
    let input = parse_macro_input!(input as DeriveInput);
    
    // Lấy tên của struct/enum
    let name = input.ident;
    
    // Tạo implementation
    let expanded = quote! {
        impl HelloWorld for #name {
            fn hello() {
                println!("Xin chào thế giới! Tôi là {}", stringify!(#name));
            }
        }
    };
    
    TokenStream::from(expanded)
}

// Sử dụng:
#[derive(HelloWorld)]
struct Pancakes;

fn main() {
    Pancakes::hello();  // "Xin chào thế giới! Tôi là Pancakes"
}

Cấu trúc AST (syn)

rust
// syn parse Rust code thành AST
// DeriveInput đại diện cho:
pub struct DeriveInput {
    pub attrs: Vec<Attribute>,    // #[...]
    pub vis: Visibility,          // pub, pub(crate), etc
    pub ident: Ident,             // Tên của type
    pub generics: Generics,       // <T, U>
    pub data: Data,               // struct fields hoặc enum variants
}

pub enum Data {
    Struct(DataStruct),
    Enum(DataEnum),
    Union(DataUnion),
}

pub struct DataStruct {
    pub fields: Fields,  // Named, Unnamed, hoặc Unit
}

Ví dụ Attribute Macro

rust
// Attribute macro: transform toàn bộ item
#[proc_macro_attribute]
pub fn log_calls(attr: TokenStream, item: TokenStream) -> TokenStream {
    let input = parse_macro_input!(item as syn::ItemFn);
    let name = &input.sig.ident;
    let body = &input.block;
    let attrs = &input.attrs;
    let vis = &input.vis;
    let sig = &input.sig;
    
    let expanded = quote! {
        #(#attrs)*
        #vis #sig {
            println!("Đang vào: {}", stringify!(#name));
            let result = #body;
            println!("Đang ra: {}", stringify!(#name));
            result
        }
    };
    
    TokenStream::from(expanded)
}

// Sử dụng:
#[log_calls]
fn add(a: i32, b: i32) -> i32 {
    a + b
}

Ví dụ Function-like Macro

rust
#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {
    let input = input.to_string();
    
    // Parse SQL tại compile time
    // Validate cú pháp, trích xuất parameters
    // Tạo type-safe query struct
    
    let expanded = quote! {
        // Code được tạo ở đây
    };
    
    TokenStream::from(expanded)
}

// Sử dụng:
let query = sql!(SELECT * FROM users WHERE id = ?);

3. Advanced Macro Patterns

Recursive Macros

rust
macro_rules! count_exprs {
    () => { 0 };
    ($head:expr) => { 1 };
    ($head:expr, $($tail:expr),*) => {
        1 + count_exprs!($($tail),*)
    };
}

let n = count_exprs!(a, b, c, d);  // 4

TT Muncher Pattern

rust
// Xử lý tokens từng cái một
macro_rules! mixed_up {
    // Base case
    () => {};
    
    // Match và transform
    (@ $e:expr, $($rest:tt)*) => {
        println!("At: {}", $e);
        mixed_up!($($rest)*);
    };
    
    (# $e:expr, $($rest:tt)*) => {
        println!("Hash: {}", $e);
        mixed_up!($($rest)*);
    };
}

mixed_up!(@ 1, # 2, @ 3,);
// In ra:
// At: 1
// Hash: 2
// At: 3
))}

Internal Rules

rust
macro_rules! foo {
    // Giao diện công khai
    ($e:expr) => {
        foo!(@internal $e)
    };
    
    // Rule nội bộ (quy ước bắt đầu với @)
    (@internal $e:expr) => {
        $e * 2
    };
}

🎯 Best Practices

Khi nào dùng cái nào?

Đặc điểmDeclarativeProcedural
Độ phức tạpPatterns đơn giảnLogic phức tạp
Hiệu năngBiên dịch nhanhChậm hơn
DebugKhóDễ hơn một chút
Use caseDSL, lặp lạiDerive, validation

Debug Macro

rust
// Mở rộng macros để xem output
cargo expand

// Hoặc dùng unstable feature
#![feature(trace_macros)]
trace_macros!(true);
my_macro!(...);
trace_macros!(false);

Lỗi thường gặp

rust
// ❌ Trộn expression và statement context
macro_rules! bad {
    ($e:expr) => {
        let x = $e;  // Đây là statement, không phải expression
    };
}

// ✅ Dùng block để tạo expression
macro_rules! good {
    ($e:expr) => {
        {
            let x = $e;
            x
        }
    };
}