第十三章:宏系统——元编程的艺术

第十三章:宏系统——元编程的艺术

本章导读:想象你在填写表格,每张表格结构相同,只是内容不同。如果有一种方式能自动生成表格,岂不是省去大量重复工作?宏就是这样的"表格生成器"——它写代码的代码。Rust 的宏系统强大而安全,让你在不牺牲类型安全的前提下,实现编译期的代码生成。


🎭 13.1 宏 vs 函数

📊 13.1.1 核心区别

特性函数
调用时机运行时编译时
参数数量固定可变
类型检查定义时展开后
能否生成代码
递归容易需要技巧
// 函数:固定参数,运行时调用
fn add(a: i32, b: i32) -> i32 {
    a + b
}

// 宏:可变参数,编译时展开
macro_rules! add {
    ($a:expr) => { $a };
    ($a:expr, $($rest:expr),+) => {
        $a + add!($($rest),+)
    };
}

fn main() {
    println!("函数: {}", add(1, 2));
    println!("宏: {}", add!(1, 2, 3, 4, 5));  // 可变参数!
}

🎯 13.1.2 宏的应用场景

// 1. 可变参数
println!("Hello");
println!("{} + {} = {}", 1, 2, 3);

// 2. 代码生成
vec![1, 2, 3];  // 展开为 Vec::new() + push

// 3. DSL(领域特定语言)
sqlx::query!("SELECT * FROM users WHERE id = ?", user_id);

// 4. 编译期检查
const _: () = assert!(std::mem::size_of::<usize>() == 8);

📝 13.2 声明式宏:macro_rules!

声明式宏使用模式匹配来生成代码。

🌱 13.2.1 基本语法

macro_rules! 宏名 {
    (模式) => { 展开代码 };
    // 多个分支
    (模式1) => { 代码1 };
    (模式2) => { 代码2 };
}

// 调用方式
宏名!(参数);
宏名![参数];
宏名!{参数};

🎯 13.2.2 片段分类符

macro_rules! demo {
    // expr: 表达式(1 + 2, func(), etc.)
    ($e:expr) => { println!("表达式: {}", $e) };

    // ident: 标识符(变量名、函数名)
    ($i:ident) => { println!("标识符: {}", stringify!($i)) };

    // ty: 类型(i32, String, Vec<u8>)
    ($t:ty) => { println!("类型: {}", stringify!($t)) };

    // literal: 字面量(1, "hello", 'c')
    ($l:literal) => { println!("字面量: {}", $l) };

    // stmt: 语句(let x = 1;)
    ($s:stmt) => { println!("语句: {}", stringify!($s)) };

    // path: 路径(std::collections::HashMap)
    ($p:path) => { println!("路径: {}", stringify!($p)) };

    // tt: 标记树(任何标记)
    ($($tt:tt)*) => { println!("标记树: {}", stringify!($($tt)*)) };

    // block: 代码块 { ... }
    ($b:block) => { println!("代码块: {}", stringify!($b)) };
}

fn main() {
    demo!(1 + 2);           // expr
    demo!(my_variable);     // ident
    demo!(Vec<String>);     // ty
    demo!(42);              // literal
    demo!(let x = 1;);      // stmt
    demo!(std::mem::size_of); // path
    demo!({ 1 + 2 });       // block
}

🔄 13.2.3 重复模式

macro_rules! vec_demo {
    // 空
    () => { Vec::new() };

    // 一个元素
    ($elem:expr) => {
        {
            let mut v = Vec::new();
            v.push($elem);
            v
        }
    };

    // 多个元素:$() 包裹重复部分, * 表示0次或多次
    ($($elem:expr),+ $(,)?) => {
        {
            let mut v = Vec::new();
            $(v.push($elem);)+  // 重复 push
            v
        }
    };
}

fn main() {
    let v1 = vec_demo!();
    let v2 = vec_demo!(1);
    let v3 = vec_demo!(1, 2, 3);

    println!("{:?} {:?} {:?}", v1, v2, v3);
}

重复符号说明:

  • *:0次或多次
  • +:1次或多次
  • ?:0次或1次
  • $(...):分组
  • $() 后的 ;,:分隔符
  • $(,)?:可选的尾随逗号

🔧 13.2.4 实用宏示例

打印变量名和值

macro_rules! dbg {
    ($var:ident) => {
        println!("{} = {:?}", stringify!($var), $var);
    };
    ($($var:ident),+ $(,)?) => {
        $(println!("{} = {:?}", stringify!($var), $var);)+
    };
}

fn main() {
    let name = "Rust";
    let version = 2021;
    dbg!(name);
    dbg!(name, version);
}

简化结构体构造

macro_rules! struct_builder {
    ($name:ident { $($field:ident: $type:ty),* $(,)? }) => {
        pub struct $name {
            $(pub $field: $type,)*
        }

        impl $name {
            pub fn new() -> Self {
                Self {
                    $($field: Default::default(),)*
                }
            }

            $(
                pub fn $field(mut self, value: $type) -> Self {
                    self.$field = value;
                    self
                }
            )*
        }
    };
}

// 使用宏生成结构体
struct_builder!(Person {
    name: String,
    age: u32,
});

fn main() {
    let person = Person::new()
        .name("Alice".to_string())
        .age(30);

    println!("{} is {}", person.name, person.age);
}

🧪 13.3 过程宏

过程宏是 Rust 函数,接收 TokenStream 作为输入,返回 TokenStream 作为输出。

📦 13.3.1 创建过程宏项目

# 过程宏必须在单独的 crate 中
cargo new my_macros --lib
# Cargo.toml
[lib]
proc-macro = true

[dependencies]
syn = "2.0"
quote = "1.0"
proc-macro2 = "1.0"

🏷️ 13.3.2 派生宏

// my_macros/src/lib.rs
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};

#[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 #name {
            pub fn say_hello(&self) {
                println!("Hello from {}!", stringify!(#name));
            }
        }
    };

    TokenStream::from(expanded)
}

使用派生宏:

// 主项目中
use my_macros::Hello;

#[derive(Hello)]
struct MyStruct {
    value: i32,
}

fn main() {
    let obj = MyStruct { value: 42 };
    obj.say_hello();  // "Hello from MyStruct!"
}

🔧 13.3.3 属性宏

// my_macros/src/lib.rs
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemFn};

#[proc_macro_attribute]
pub fn log_calls(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let input = parse_macro_input!(item as ItemFn);
    let fn_name = &input.sig.ident;
    let fn_block = &input.block;

    let expanded = quote! {
        fn #fn_name() {
            println!("调用函数: {}", stringify!(#fn_name));
            #fn_block
            println!("函数完成: {}", stringify!(#fn_name));
        }
    };

    TokenStream::from(expanded)
}

使用属性宏:

#[log_calls]
fn do_something() {
    println!("执行某些操作...");
}

fn main() {
    do_something();
    // 输出:
    // 调用函数: do_something
    // 执行某些操作...
    // 函数完成: do_something
}

📞 13.3.4 函数式宏

// my_macros/src/lib.rs
use proc_macro::TokenStream;
use quote::quote;
use syn::parse::{Parse, ParseStream};
use syn::{Ident, LitInt, Token};

struct RepeatArgs {
    name: Ident,
    times: LitInt,
}

impl Parse for RepeatArgs {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let name: Ident = input.parse()?;
        input.parse::<Token![,]>()?;
        let times: LitInt = input.parse()?;
        Ok(RepeatArgs { name, times })
    }
}

#[proc_macro]
pub fn repeat(input: TokenStream) -> TokenStream {
    let args = syn::parse_macro_input!(input as RepeatArgs);
    let name = &args.name;
    let times: usize = args.times.base10_parse().unwrap();

    let expanded = quote! {
        {
            for _ in 0..#times {
                println!("{}", stringify!(#name));
            }
        }
    };

    TokenStream::from(expanded)
}

使用函数式宏:

use my_macros::repeat;

fn main() {
    repeat!(Rust, 3);
    // 输出:
    // Rust
    // Rust
    // Rust
}

🔬 13.4 宏调试技巧

📋 13.4.1 cargo expand

查看宏展开后的代码:

cargo install cargo-expand
cargo expand

🔍 13.4.2 内置调试宏

macro_rules! debug_macro {
    ($($tt:tt)*) => {
        {
            // 打印原始输入
            println!("输入: {}", stringify!($($tt)*));

            // 在编译时警告
            // compile_error!("调试:查看宏展开");
            $($tt)*
        }
    };
}

fn main() {
    debug_macro!(let x = 1 + 2;);
    // 输出: 输入: let x = 1 + 2
}

🛡️ 13.4.3 卫生性(Hygiene)

Rust 宏是卫生的——宏内定义的变量不会与外部冲突:

macro_rules! confusion {
    () => {
        let x = 1;  // 宏内的 x
        println!("宏内 x: {}", x);
    };
}

fn main() {
    let x = 100;  // 外部的 x
    confusion!();
    println!("外部 x: {}", x);  // 仍然是 100
}

第一性原理:宏卫生防止了变量名意外捕获,这是 Rust 宏系统区别于 C 宏的关键特性。


🧪 13.5 实战:构建一个简单的测试框架

让我们用宏创建一个迷你的测试框架:

// test_framework/lib.rs

use std::sync::atomic::{AtomicUsize, Ordering};

static TESTS_REGISTERED: AtomicUsize = AtomicUsize::new(0);
static TESTS_PASSED: AtomicUsize = AtomicUsize::new(0);

// 测试函数类型
type TestFn = fn() -> Result<(), String>;

// 存储测试用例
thread_local! {
    static TESTS: std::cell::RefCell<Vec<(&'static str, TestFn)>> =
        std::cell::RefCell::new(Vec::new());
}

// 注册测试
pub fn register_test(name: &'static str, test: TestFn) {
    TESTS.with(|tests| {
        tests.borrow_mut().push((name, test));
    });
    TESTS_REGISTERED.fetch_add(1, Ordering::SeqCst);
}

// 运行所有测试
pub fn run_tests() {
    println!("\n===== 运行测试 =====\n");

    TESTS.with(|tests| {
        for (name, test) in tests.borrow().iter() {
            match test() {
                Ok(()) => {
                    println!("✅ {} ... 通过", name);
                    TESTS_PASSED.fetch_add(1, Ordering::SeqCst);
                }
                Err(e) => {
                    println!("❌ {} ... 失败", name);
                    println!("   错误: {}", e);
                }
            }
        }
    });

    let registered = TESTS_REGISTERED.load(Ordering::SeqCst);
    let passed = TESTS_PASSED.load(Ordering::SeqCst);

    println!("\n===== 测试结果 =====");
    println!("通过: {}/{}", passed, registered);

    if passed == registered {
        println!("🎉 所有测试通过!");
    }
}

// 测试宏
#[macro_export]
macro_rules! test {
    ($name:ident $block:block) => {
        #[allow(non_snake_case)]
        fn $name() -> Result<(), String> $block

        // 使用 ctor 在 main 前注册(或手动调用)
        // 这里简化为手动注册
    };
}

// 断言宏
#[macro_export]
macro_rules! assert_ok {
    ($expr:expr) => {
        match $expr {
            Ok(v) => v,
            Err(e) => return Err(format!("断言失败: {:?}", e)),
        }
    };
}

#[macro_export]
macro_rules! assert_eq_msg {
    ($left:expr, $right:expr) => {
        if $left != $right {
            return Err(format!(
                "断言失败: {} != {}",
                stringify!($left),
                stringify!($right)
            ));
        }
    };
}

使用示例:

fn test_addition() -> Result<(), String> {
    assert_eq_msg!(1 + 1, 2);
    assert_eq_msg!(2 + 2, 4);
    Ok(())
}

fn test_division() -> Result<(), String> {
    let result = 10 / 2;
    assert_eq_msg!(result, 5);
    Ok(())
}

fn test_failure_example() -> Result<(), String> {
    assert_eq_msg!(1 + 1, 3);  // 这会失败
    Ok(())
}

fn main() {
    // 手动注册测试
    register_test("test_addition", test_addition);
    register_test("test_division", test_division);
    register_test("test_failure", test_failure_example);

    run_tests();
}

⚠️ 13.6 宏的最佳实践

✅ 应该用宏的场景

// 1. 消除样板代码
macro_rules! getters {
    ($($field:ident: $type:ty),*) => {
        $(
            pub fn $field(&self) -> &$type {
                &self.$field
            }
        )*
    };
}

// 2. 创建 DSL
macro_rules! route {
    (GET $path:expr => $handler:expr) => {
        println!("注册 GET {} -> {}", $path, stringify!($handler));
    };
    (POST $path:expr => $handler:expr) => {
        println!("注册 POST {} -> {}", $path, stringify!($handler));
    };
}

// 3. 条件编译
macro_rules! platform_message {
    () => {
        #[cfg(target_os = "linux")]
        println!("Running on Linux");
        #[cfg(target_os = "windows")]
        println!("Running on Windows");
        #[cfg(target_os = "macos")]
        println!("Running on macOS");
    };
}

❌ 不应该用宏的场景

// 不好:简单的操作用宏
macro_rules! add {
    ($a:expr, $b:expr) => { $a + $b };
}

// 好:用函数
fn add(a: i32, b: i32) -> i32 {
    a + b
}

// 不好:隐藏复杂逻辑
macro_rules! magic {
    ($x:expr) => {
        // 100行复杂代码...
    };
}

// 好:使用函数或方法,清晰明了
fn process(x: i32) -> i32 {
    // 复杂但可读的逻辑
    x * 2
}

费曼技巧提问:为什么宏的代码更难调试?提示:想想编译器看到的代码和你看到的代码有什么区别。


📝 本章小结

本章我们学习了 Rust 的宏系统:

宏类型语法用途
声明式宏macro_rules!模式匹配,代码生成
派生宏#[procmacroderive]自动实现 trait
属性宏#[procmacroattribute]修改/增强项目
函数式宏#[proc_macro]自定义语法

关键概念:

  • TokenStream:标记流,宏操作的基本单位
  • 卫生性:宏内变量不污染外部作用域
  • 片段分类符:匹配不同类型的代码片段

记住:宏很强大,但应该谨慎使用。能用手写代码解决的问题,就不要用宏。


动手实验

  1. 编写一个 hashmap! 宏,像 vec! 一样便捷地创建 HashMap。
  2. 编写一个 measure! 宏,测量并打印代码块的执行时间。
  3. 创建一个派生宏 #[derive(Builder)],为结构体生成建造者模式代码。
  4. 解释为什么以下宏会编译失败,如何修复:

``rust macro_rules! assign { ($var:ident = $val:expr) => { let $var = $val; }; } fn main() { assign!(x = 1); assign!(x = 2); // 错误:重复定义 } ``

← 返回目录