bigywater.tech
Rust · 77

声明宏 macro_rules!

macro_rules! 是 Rust 最常用的宏机制,但它的规则、展开和陷阱都不直观。这篇从最小的例子开始,让编译器展开宏,验证表达式被重复求值的坑,以及编译错误长什么样。

2 次
square!(next()) 中 next() 被展开并求值的次数(函数只有 1 次)
25
square!(2 + 3) 的结果(整体,不是 11)
5
tt 递归计数 count!(a b c d e) 的结果

第一个宏

实测 · 本机执行输出
  源码:
      1 | macro_rules! square { ($x:expr) => { $x * $x }; }
      2 | macro_rules! my_vec {
      3 |     () => { Vec::new() };
      4 |     ($($x:expr),+ $(,)?) => {{ let mut v = Vec::new(); $( v.push($x); )+ v }};
      5 | }
      6 | macro_rules! maxm {
      7 |     ($x:expr) => { $x };
      8 |     ($x:expr, $($rest:expr),+) => {{ let a = $x; let b = maxm!($($rest),+); if a > b { a } else { b } }};
      9 | }
     10 | fn main() {
     11 |     println!("square!(7) = {}", square!(7));
     12 |     let e: Vec<i32> = my_vec![]; let v = my_vec![1, 2, 3,];
     13 |     println!("my_vec![] = {:?}   my_vec![1,2,3,] = {:?}", e, v);
     14 |     println!("maxm!(3, 9, 4, 7) = {}   maxm!(5) = {}", maxm!(3, 9, 4, 7), maxm!(5));
     15 | }
  编译:成功
  运行(退出码 0):
    square!(7) = 49
    my_vec![] = []   my_vec![1,2,3,] = [1, 2, 3]
    maxm!(3, 9, 4, 7) = 9   maxm!(5) = 5

编译器的展开

实测 · 本机执行输出
  源码:
    macro_rules! square { ($x:expr) => { $x * $x }; }
    macro_rules! my_vec { ($($x:expr),+) => {{ let mut v = Vec::new(); $( v.push($x); )+ v }}; }
    pub fn demo() -> (i32, Vec<i32>) { (square!(2 + 3), my_vec![1, 2, 3]) }
  展开后:
    macro_rules! square { ($x:expr) => { $x * $x }; }
    macro_rules! my_vec {
        ($($x:expr),+) => {{ let mut v = Vec::new(); $(v.push($x);)+ v }};
    }
    pub fn demo() -> (i32, Vec<i32>) {
        ((2 + 3) * (2 + 3),
            { let mut v = Vec::new(); v.push(1); v.push(2); v.push(3); v })
    }

展开用 RUSTC_BOOTSTRAP=1 与 -Z unpretty=expanded;输出里的括号来自编译器对表达式片段的打印。宏的卫生性见宏卫生性。

表达式被重复求值

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    square!(2 + 3) = 25(2+3 被当作整体)
    square!(next()) = 2:宏把 next() 展开了两次(1 * 2),调用次数 2
    square_fn(next()) = 1:函数只求值一次,调用次数 1

编译错误

实测 · 本机执行输出
  源码:
      1 | macro_rules! pair { ($a:expr, $b:expr) => { ($a, $b) }; }
      2 | fn main() { let p = pair!(1); println!("{:?}", p); }
  编译:失败
    error: unexpected end of macro invocation
     --> <实验目录>/src/u77d.rs:2:28
      |
    1 | macro_rules! pair { ($a:expr, $b:expr) => { ($a, $b) }; }
      | ----------------- when calling this macro
    2 | fn main() { let p = pair!(1); println!("{:?}", p); }
      |                            ^ missing tokens in macro arguments
      |
    note: while trying to match `,`
     --> <实验目录>/src/u77d.rs:1:29
      |
    1 | macro_rules! pair { ($a:expr, $b:expr) => { ($a, $b) }; }
      |                             ^
    
    error: aborting due to 1 previous error
  源码:
      1 | fn main() { println!("{}", late!(2)); }
      2 | macro_rules! late { ($x:expr) => { $x + 1 }; }
  编译:失败
    error: cannot find macro `late` in this scope
     --> <实验目录>/src/u77e.rs:1:28
      |
    1 | fn main() { println!("{}", late!(2)); }
      |                            ^^^^ consider moving the definition of `late` before this call
      |
    note: a macro with the same name exists, but it appears later
     --> <实验目录>/src/u77e.rs:2:14
      |
    2 | macro_rules! late { ($x:expr) => { $x + 1 }; }
      |              ^^^^
    
    warning: unused macro definition: `late`
     --> <实验目录>/src/u77e.rs:2:14
      |
    2 | macro_rules! late { ($x:expr) => { $x + 1 }; }
      |              ^^^^
      |
      = note: `#[warn(unused_macros)]` (part of `#[warn(unused)]`) on by default
    
    error: aborting due to 1 previous error; 1 warning emitted

常用的片段类型

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    seven() = 7  forty_two() = 42
    Point { x: 0, y: 0, tag: "" }
    op!(3 plus 4) = 7  op!(3 times 4) = 12
    count!(a b c d e) = 5

分步演示

macro_rules!
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。展开用 RUSTC_BOOTSTRAP=1 -Z unpretty=expanded
    • 没有验证:#[macro_export] 与跨 crate 的宏作用域、$crate、宏递归深度上限(recursion_limit)、宏 2.0(不稳定)、其它片段类型(pat、vis、lifetime、meta、item)
    京ICP备2026067502号-1京公网安备11010502063487号