宏系统与元编程

本章目标: 掌握声明宏和过程宏,理解 Rust 元编程的边界

学习时间: 5-7 天

前置知识: 完成 07-智能指针与内存管理.md


宏的种类

Rust 宏体系:
                    
    声明宏                     过程宏
    (Declarative)              (Procedural)
    ----------                 ----------
    macro_rules!               #[derive(...)]  派生宏
                               #[attr]         属性宏
                               func!(...)      函数式宏

1. 声明宏 (macro_rules!)

基础语法

// 最简单的宏
macro_rules! say_hello {
    () => {
        println!("Hello!");
    };
}
 
// 带参数的宏
macro_rules! print_expr {
    ($e:expr) => {
        println!("{} = {:?}", stringify!($e), $e);
    };
}
 
fn main() {
    say_hello!();
    
    let x = 5 + 3;
    print_expr!(x);        // x = 8
    print_expr!(5 + 3);    // 5 + 3 = 8
}

捕获类型 (Fragment Specifiers)

macro_rules! demo_fragments {
    // expr: 表达式
    (expr $e:expr) => { println!("expr: {:?}", $e) };
    
    // stmt: 语句
    (stmt $s:stmt) => { $s };
    
    // ty: 类型
    (ty $t:ty) => { println!("type") };
    
    // ident: 标识符 (变量名、函数名等)
    (ident $i:ident) => { println!("ident: {}", stringify!($i)) };
    
    // pat: 模式
    (pat $p:pat) => { println!("pattern") };
    
    // path: 路径 (如 std::vec::Vec)
    (path $p:path) => { println!("path") };
    
    // tt: 单个 token 或括号内的 token 树 (最灵活)
    (tt $t:tt) => { println!("token tree") };
    
    // literal: 字面量
    (lit $l:literal) => { println!("literal: {}", $l) };
    
    // block: 代码块 {}
    (block $b:block) => { $b };
    
    // item: 条目 (fn/struct/impl等)
    (item $i:item) => { $i };
}

重复模式

// $( ... )? - 零或一次
// $( ... )* - 零或多次
// $( ... )+ - 一或多次
 
// 实现 vec! 宏
macro_rules! my_vec {
    // 无元素
    () => {
        Vec::new()
    };
    // 一或多个元素
    ($($elem:expr),+ $(,)?) => {
        {
            let mut v = Vec::new();
            $( v.push($elem); )+
            v
        }
    };
}
 
// 实现 hashmap! 宏
macro_rules! hashmap {
    () => { std::collections::HashMap::new() };
    ($($key:expr => $val:expr),+ $(,)?) => {
        {
            let mut map = std::collections::HashMap::new();
            $( map.insert($key, $val); )+
            map
        }
    };
}
 
fn main() {
    let v = my_vec![1, 2, 3, 4, 5];
    println!("{:?}", v);
    
    let m = hashmap! {
        "name" => "Alice",
        "city" => "Beijing",
    };
    println!("{:?}", m);
}

递归宏

// 使用递归实现更复杂的模式
 
// 实现 println! 的简化版
macro_rules! my_print {
    ($fmt:literal) => {
        print!($fmt);
    };
    ($fmt:literal, $($args:expr),*) => {
        print!($fmt, $($args),*);
    };
}
 
// 实现 count! - 计算参数个数
macro_rules! count {
    () => { 0usize };
    ($head:tt $($tail:tt)*) => { 1 + count!($($tail)*) };
}
 
fn main() {
    println!("count: {}", count!(a b c d e));  // 5
}

实用宏示例

// 1. 调试宏
macro_rules! dbg_val {
    ($val:expr) => {
        {
            let v = $val;
            eprintln!("[{}:{}] {} = {:?}", file!(), line!(), stringify!($val), v);
            v
        }
    };
}
 
// 2. 断言宏 (带自定义消息)
macro_rules! ensure {
    ($cond:expr, $($msg:tt)*) => {
        if !($cond) {
            return Err(format!($($msg)*));
        }
    };
}
 
fn validate_age(age: i32) -> Result<(), String> {
    ensure!(age >= 0, "Age cannot be negative, got {}", age);
    ensure!(age <= 150, "Age {} is unreasonably large", age);
    Ok(())
}
 
// 3. 枚举字符串转换宏
macro_rules! str_enum {
    ($name:ident { $($variant:ident),+ }) => {
        #[derive(Debug, Clone, PartialEq)]
        pub enum $name {
            $($variant),+
        }
        
        impl std::fmt::Display for $name {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                match self {
                    $( $name::$variant => write!(f, stringify!($variant)), )+
                }
            }
        }
        
        impl std::str::FromStr for $name {
            type Err = String;
            fn from_str(s: &str) -> Result<Self, Self::Err> {
                match s {
                    $( stringify!($variant) => Ok($name::$variant), )+
                    _ => Err(format!("Unknown variant: {}", s)),
                }
            }
        }
    };
}
 
str_enum!(Color { Red, Green, Blue });
 
fn main() {
    let c: Color = "Red".parse().unwrap();
    println!("{}", c);  // Red
    println!("{:?}", c);  // Red
}

2. 过程宏基础

过程宏是编译时运行的 Rust 函数,接受 TokenStream 并返回 TokenStream。

项目结构

my_macros/           # 必须是单独的 crate!
├── Cargo.toml
└── src/
    └── lib.rs

my_app/
├── Cargo.toml       # 依赖 my_macros
└── src/
    └── main.rs
# my_macros/Cargo.toml
[lib]
proc-macro = true
 
[dependencies]
proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "2.0", features = ["full"] }

核心工具库

proc-macro2: TokenStream 类型 (可组合)
syn:        解析 Rust 语法树
quote:      从代码模板生成 TokenStream

3. 派生宏 (#[derive(…)])

// my_macros/src/lib.rs
 
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput, Data, Fields};
 
// 实现 #[derive(Hello)]
#[proc_macro_derive(Hello)]
pub fn hello_derive(input: TokenStream) -> TokenStream {
    // 解析输入为语法树
    let input = parse_macro_input!(input as DeriveInput);
    
    let name = &input.ident;
    
    // 生成实现代码
    let expanded = quote! {
        impl Hello for #name {
            fn hello(&self) -> String {
                format!("Hello, I am {}!", stringify!(#name))
            }
        }
    };
    
    TokenStream::from(expanded)
}
// 使用:
use my_macros::Hello;
 
pub trait Hello {
    fn hello(&self) -> String;
}
 
#[derive(Hello)]
struct MyStruct {
    name: String,
}
 
fn main() {
    let s = MyStruct { name: "Alice".into() };
    println!("{}", s.hello());  // Hello, I am MyStruct!
}

带字段的派生宏

// 实现 #[derive(Builder)] - 自动生成 Builder 模式
#[proc_macro_derive(Builder)]
pub fn builder_derive(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;
    
    let builder_name = syn::Ident::new(
        &format!("{}Builder", name),
        name.span()
    );
    
    // 提取字段
    let fields = match &input.data {
        Data::Struct(s) => match &s.fields {
            Fields::Named(f) => &f.named,
            _ => panic!("Only named fields supported"),
        },
        _ => panic!("Only structs supported"),
    };
    
    // 生成 Builder 字段 (用 Option 包裹)
    let builder_fields = fields.iter().map(|f| {
        let fname = &f.ident;
        let ftype = &f.ty;
        quote! { #fname: Option<#ftype> }
    });
    
    // 生成 setter 方法
    let setters = fields.iter().map(|f| {
        let fname = &f.ident;
        let ftype = &f.ty;
        quote! {
            pub fn #fname(mut self, value: #ftype) -> Self {
                self.#fname = Some(value);
                self
            }
        }
    });
    
    // 生成 build 方法
    let build_fields = fields.iter().map(|f| {
        let fname = &f.ident;
        quote! {
            #fname: self.#fname.ok_or(
                concat!("Field '", stringify!(#fname), "' not set")
            )?
        }
    });
    
    let expanded = quote! {
        // Builder 结构体
        pub struct #builder_name {
            #(#builder_fields,)*
        }
        
        impl #builder_name {
            #(#setters)*
            
            pub fn build(self) -> Result<#name, &'static str> {
                Ok(#name {
                    #(#build_fields,)*
                })
            }
        }
        
        impl #name {
            pub fn builder() -> #builder_name {
                #builder_name {
                    #(#(#fields.ident)?: None,)*
                }
            }
        }
    };
    
    // 注意: 上面的展开有语法问题, 实际项目中需要更仔细处理字段初始化
    TokenStream::from(expanded)
}

4. 属性宏

属性宏可以修改被标注的条目:

// 实现 #[logged] 属性宏 - 自动记录函数调用
#[proc_macro_attribute]
pub fn logged(args: TokenStream, input: TokenStream) -> TokenStream {
    let input_fn = parse_macro_input!(input as syn::ItemFn);
    
    let fn_name = &input_fn.sig.ident;
    let fn_block = &input_fn.block;
    let fn_sig = &input_fn.sig;
    let fn_vis = &input_fn.vis;
    
    let expanded = quote! {
        #fn_vis #fn_sig {
            println!("[LOG] Entering {}", stringify!(#fn_name));
            let result = (|| #fn_block)();
            println!("[LOG] Exiting {}", stringify!(#fn_name));
            result
        }
    };
    
    TokenStream::from(expanded)
}
// 使用:
use my_macros::logged;
 
#[logged]
fn process_data(x: i32) -> i32 {
    x * 2
}
 
fn main() {
    let result = process_data(21);
    println!("Result: {}", result);
}
// 输出:
// [LOG] Entering process_data
// [LOG] Exiting process_data
// Result: 42

5. 函数式过程宏

// 实现 sql! 宏 - 编译时验证 SQL 语法
#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {
    let input_str = input.to_string();
    
    // 简单验证 (实际应该用 SQL 解析器)
    if !input_str.trim_matches('"').to_uppercase().starts_with("SELECT") 
        && !input_str.trim_matches('"').to_uppercase().starts_with("INSERT")
        && !input_str.trim_matches('"').to_uppercase().starts_with("UPDATE")
        && !input_str.trim_matches('"').to_uppercase().starts_with("DELETE")
    {
        // 编译时错误
        return quote! {
            compile_error!("Only SELECT/INSERT/UPDATE/DELETE are supported")
        }.into();
    }
    
    // 返回字符串
    quote! { #input_str }.into()
}

6. 调试技巧

cargo-expand

# 安装
cargo install cargo-expand
 
# 展开宏
cargo expand              # 所有宏
cargo expand src/main.rs  # 特定文件
cargo expand --test       # 测试文件中的宏

eprintln! 调试过程宏

#[proc_macro_derive(Debug2)]
pub fn debug2_derive(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    
    // 打印 AST (调试用)
    eprintln!("Input AST: {:#?}", input);
    
    // 生成代码
    let name = &input.ident;
    let expanded = quote! {
        impl std::fmt::Debug for #name {
            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
                write!(f, "{}", stringify!(#name))
            }
        }
    };
    
    // 打印生成的代码 (调试用)
    eprintln!("Generated: {}", expanded);
    
    TokenStream::from(expanded)
}

常见错误

// 错误1: 卫生性问题 (Hygiene)
macro_rules! bad_swap {
    ($a:expr, $b:expr) => {
        let tmp = $a;  // tmp 在宏内部是独立的
        $a = $b;
        $b = tmp;      // 这里 tmp 是宏内的 tmp, 不是外部同名变量
    };
}
 
// 正确: 使用块表达式
macro_rules! good_swap {
    ($a:ident, $b:ident) => {
        {
            let tmp = $a;
            $a = $b;
            $b = tmp;
        }
    };
}
 
// 错误2: $crate 的使用
// 在库宏中引用本 crate 的路径要用 $crate::
macro_rules! create_error {
    ($msg:literal) => {
        $crate::Error::new($msg)  // 正确! 不要用 crate::Error
    };
}

练习题

练习 1: 实现 assert_eq_approx! 宏

// 实现浮点数近似相等断言
macro_rules! assert_approx_eq {
    ($a:expr, $b:expr) => {
        assert_approx_eq!($a, $b, 1e-6)
    };
    ($a:expr, $b:expr, $eps:expr) => {
        {
            let a = $a;
            let b = $b;
            let eps = $eps;
            if (a - b).abs() > eps {
                panic!(
                    "assertion failed: `(left ≈ right)`\n  left: `{:?}`,\n right: `{:?}`,\n   eps: `{:?}`",
                    a, b, eps
                );
            }
        }
    };
}
 
fn main() {
    assert_approx_eq!(0.1 + 0.2, 0.3);       // 通过 (在 1e-6 精度内)
    assert_approx_eq!(1.0, 1.0000001, 1e-5);  // 通过
    // assert_approx_eq!(1.0, 2.0);           // panic!
}

练习 2: 实现 lazy_static! 宏

// 实现线程安全的懒加载宏 (简化版)
use std::sync::OnceLock;
 
macro_rules! lazy_static_simple {
    (static ref $name:ident: $type:ty = $init:expr;) => {
        static $name: OnceLock<$type> = OnceLock::new();
        
        // 注意: OnceLock 已经提供了 get_or_init
        // 实际 lazy_static 更复杂, 这里演示原理
    };
}
 
// 更简洁的方式: 使用 std::sync::OnceLock
static GLOBAL_CONFIG: OnceLock<std::collections::HashMap<String, String>> = OnceLock::new();
 
fn get_config() -> &'static std::collections::HashMap<String, String> {
    GLOBAL_CONFIG.get_or_init(|| {
        let mut m = std::collections::HashMap::new();
        m.insert("host".to_string(), "localhost".to_string());
        m.insert("port".to_string(), "8080".to_string());
        m
    })
}
 
fn main() {
    println!("host = {}", get_config().get("host").unwrap());
}

练习 3: 实现状态机宏

macro_rules! state_machine {
    (
        enum $name:ident {
            $($state:ident),+
        }
        transitions {
            $($from:ident -> $to:ident via $event:ident),+
        }
    ) => {
        #[derive(Debug, Clone, PartialEq)]
        pub enum $name {
            $($state),+
        }
        
        impl $name {
            pub fn transition(&self, event: &str) -> Option<$name> {
                match (self, event) {
                    $( ($name::$from, stringify!($event)) => Some($name::$to), )+
                    _ => None,
                }
            }
        }
    };
}
 
state_machine! {
    enum TrafficLight {
        Red,
        Yellow,
        Green
    }
    transitions {
        Red -> Green via go,
        Green -> Yellow via slow,
        Yellow -> Red via stop
    }
}
 
fn main() {
    let mut state = TrafficLight::Red;
    println!("{:?}", state);
    
    state = state.transition("go").unwrap_or(state.clone());
    println!("{:?}", state);  // Green
    
    state = state.transition("slow").unwrap_or(state.clone());
    println!("{:?}", state);  // Yellow
}

练习 4: 使用 cargo-expand 分析

// 展开 derive(Debug) 后是什么?
#[derive(Debug)]
struct Point {
    x: f64,
    y: f64,
}
 
// 运行: cargo expand
// 会看到类似:
// impl std::fmt::Debug for Point {
//     fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
//         f.debug_struct("Point")
//             .field("x", &self.x)
//             .field("y", &self.y)
//             .finish()
//     }
// }

练习 5: 自定义 println! 变体

// 带时间戳的日志宏
macro_rules! log {
    ($level:ident, $($args:tt)*) => {
        eprintln!(
            "[{level}] {msg}",
            level = stringify!($level),
            msg = format!($($args)*)
        );
    };
}
 
macro_rules! info {
    ($($args:tt)*) => { log!(INFO, $($args)*) };
}
 
macro_rules! warn {
    ($($args:tt)*) => { log!(WARN, $($args)*) };
}
 
macro_rules! error {
    ($($args:tt)*) => { log!(ERROR, $($args)*) };
}
 
fn main() {
    info!("Server started on port {}", 8080);
    warn!("Memory usage: {}%", 85);
    error!("Failed to connect to {}", "database");
}

练习 6: 综合 - 实现 serde 风格的序列化宏

// 实现简单的 JSON 序列化 derive 宏 (概念演示)
// 真实项目请使用 serde crate
 
trait JsonSerialize {
    fn to_json(&self) -> String;
}
 
// 手动实现 (派生宏的效果)
struct User {
    name: String,
    age: u32,
}
 
impl JsonSerialize for User {
    fn to_json(&self) -> String {
        format!(
            r#"{{"name": "{}", "age": {}}}"#,
            self.name, self.age
        )
    }
}
 
// 假如有 #[derive(JsonSerialize)] 宏, 它应该生成上面的代码
// 这就是过程宏的作用: 消除样板代码
 
fn main() {
    let u = User {
        name: "Alice".to_string(),
        age: 30,
    };
    println!("{}", u.to_json());
}

何时使用宏?

使用宏的好场景:
✅ 消除大量样板代码 (derive)
✅ 创建 DSL (领域特定语言)
✅ 编译时验证 (SQL 语法等)
✅ 实现 variadic 函数 (vec!, println!)

避免使用宏的情况:
❌ 能用函数或泛型实现的 → 用函数/泛型
❌ 逻辑复杂但 DSL 价值不高 → 直接写代码
❌ 调试困难的复杂宏 → 考虑替代方案

经验法则:
- 如果宏调用比它实现的功能更难理解 → 不要用宏
- 如果宏减少了超过 3 处重复代码 → 可以考虑

小结

宏类型语法适用场景
macro_rules!name!(...)模式匹配展开, 简单重复代码
#[derive(X)]#[derive(X)]自动实现 trait
属性宏 #[attr]#[attr]转换代码, AOP
函数式过程宏name!(...)编译时计算, DSL

下一步

  • 完成 6 道练习题
  • 运行 cargo expand 观察标准宏的展开
  • 阅读 09-异步编程与Runtime.md
  • 尝试给自己的项目写一个简单的派生宏

掌握宏系统, 你就拥有了在 Rust 中创造语言的能力! 🦀