Rust 宏

声明宏 `macro_rules!`、过程宏三类、以及什么时候该写宏。

Advanced
Key Points声明宏按模式匹配生成代码,过程宏在编译期接收并返回 TokenStream。

两种宏

KindDeclared byExample
声明宏macro_rules!vec! println!
派生宏#[proc_macro_derive]#[derive(Debug)]
属性宏#[proc_macro_attribute]#[tokio::main]
函数宏#[proc_macro]sql!("...")

macro_rules!

Rust
macro_rules! square {
    ($x:expr) => {
        $x * $x
    };
}

macro_rules! my_vec {
    () => { Vec::new() };
    ($($x:expr),+ $(,)?) => {
        vec![$($x),+]
    };
}

fn main() {
    println!("{}", square!(5));
    let v: Vec<i32> = my_vec![1, 2, 3];
    println!("{:?}", v);
    let empty: Vec<i32> = my_vec![];
    println!("{:?}", empty);
}
Output
25
[1, 2, 3]
[]
FragmentMatches
expr表达式
ident标识符
ty类型
pat模式
stmt语句
block代码块
tt单个 token 树
item项(fn、struct 等)

卫生性

Rust
macro_rules! make_var {
    () => {
        let x = 1;          // 宏里的 x 和外面的 x 不是同一个
    };
}

fn main() {
    let x = 100;
    make_var!();
    println!("{}", x);      // 100,宏没覆盖它
}
Output
100

This is hygiene: names introduced inside a macro cannot clash with the call site. C's #define lacks it — #define SWAP(a,b) int tmp = a; ... collides with a caller's tmp.

过程宏

Rust
// 在一个单独的 crate 里(proc-macro = true)
use proc_macro::TokenStream;

#[proc_macro_derive(Hello)]
pub fn hello_derive(input: TokenStream) -> TokenStream {
    let ast = syn::parse(input).unwrap();
    impl_hello(&ast)
}
TOML
[lib]
proc-macro = true

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

Proc macros need three crates: syn parses TokenStream into an AST, quote generates code from templates, and proc-macro2 gives a testable layer. Hand-building TokenStream is unmaintainable.

常用内置宏

Rust
fn main() {
    // 断言
    assert!(1 + 1 == 2);
    assert_eq!(2 + 2, 4);
    assert_ne!(1, 2);

    // 格式化
    println!("{}", format!("a{}", 1));

    // 编译期
    println!("{}", env!("CARGO_PKG_NAME"));
    println!("{}", file!());
    println!("{}", line!());

    // 条件编译
    #[cfg(debug_assertions)]
    println!("debug 构建");

    // 提前返回
    fn f(x: Option<i32>) -> i32 {
        let Some(v) = x else { return 0 };
        v
    }
    println!("{}", f(Some(1)));

    // 编译期生成 panic
    // compile_error!("这个分支不应该被编译");
}
Output
a1
hello
src/main.rs
8
debug 构建
1

该不该写宏

  • 该写 —— 消除大量重复的样板(#[derive] 类)、DSL、编译期校验
  • 不该写 —— 用泛型或 trait 能解决的,别用宏
  • 不该写 —— 只在一处用一次的代码
Macro errors are usually poor — they point into expanded code, not your source. Using $x:expr rather than $x:tt in macro_rules! markedly improves diagnostics.

Friends & Partners