bigywater.tech
Rust · 79

过程宏的执行阶段

过程宏到底在什么时候运行?它收到什么输入?改了代码之后会不会重新运行?这篇自己写一个最小的 proc-macro crate,在宏里打印和记日志,把三种过程宏的阶段全部量出来。

编译期
过程宏运行的阶段:cargo build 时,不是 app 运行时
3 → 3 → 6
宏运行日志行数:首次 build、没改动 build、改了使用者之后
dylib
proc-macro crate 的产物类型

三种过程宏与它们运行的阶段

实测 · 本机执行输出
  mac/src/lib.rs(proc-macro crate)与 app/src/main.rs(使用者)已创建;crate 类型 proc-macro
  cargo build app 的输出(宏的 eprintln 出现在编译期):
         Locking 1 package to latest Rust 1.98.1 compatible version
    [编译期] 函数式宏 make_answer! 运行了,输入 tokens = "a b 3"
    [编译期] derive(Hello) 运行了,输入 = "struct Point;"
    [编译期] #[trace] 运行了,属性参数 = "level = 2",被修饰的条目 = "fn compute(x: i32) -> i32 { x + answer() }"
  运行 app 的输出:
    [运行期] main 开始
    [运行期] 进入 compute
    Hello from Point / 43

声明宏见声明宏 macro_rules!;derive 展开见Derive 宏如何生成代码。

宏运行几次

实测 · 本机执行输出
  第一次 build 之后日志共 3 行(3 个宏各运行 1 次)
  第二次 cargo build(没改动):日志仍是 3 行 —— 宏没有再运行;但 cargo 输出里仍能看到 [编译期] 行吗? True (cargo 会重放上次缓存的编译器输出)
  改了 main.rs 的一个字符串后再 build:日志 6 行(新增 3 行:三个宏全部重新运行,因为使用它们的 crate 整个重新编译)

proc-macro crate 的限制

实测 · 本机执行输出
  proc-macro crate 的产物:
    ['libmac-e31b78be4282dd07.dylib'] (动态库,由 rustc 在编译 app 时加载)
  在 proc-macro crate 里 pub fn 一个普通函数:
       Compiling mac v0.1.0 (<实验目录>/proj/u79/mac)
    error: `proc-macro` crate types currently cannot export any items other than functions tagged with `#[proc_macro]`, `#[proc_macro_derive]`, or `#[proc_macro_attribute]`
      --> <实验目录>/proj/u79/mac/src/lib.rs:28:1
       |
    28 | pub fn not_a_macro() {}
       | ^^^^^^^^^^^^^^^^^^^^

分步演示

过程宏的阶段
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。只用 proc_macro 标准库,不依赖 syn/quote;宏运行次数用写日志文件的方式统计
    • 没有验证:syn/quote 的解析与代码生成(在后面的 derive 一篇里用)、rust-analyzer 对过程宏的重复展开、cargo check 与 cargo build 对宏运行次数的影响、交叉编译时宏运行在哪个平台、proc-macro 的 Span 信息
    京ICP备2026067502号-1京公网安备11010502063487号