bigywater.tech
Rust · 82

Function-like Macro

函数式过程宏能在编译期运行任意 Rust 代码:算常量、解析自定义语法、读文件。但它也有陷阱:cargo 不知道宏读过什么文件。这篇三种用法都真实跑通,并复现了那个「改了文件却没生效」的坑。

832040
fib!(30) 在编译期算出的字面量
0.01 s
改了被 embed 的文件后 cargo build 的耗时(什么都没重编)
3 个
一次编译同时报出的带位置的宏错误数

编译期计算

实测 · 本机执行输出
  运行: fib!(30) = 832040   fib!(10) = 55
  展开后的 main:
    fn main() {
        const F30: u64 = 832040u64;
        {
            ::std::io::_print(format_args!("fib!(30) = {0}   fib!(10) = {1}\n",
                    F30, 55u64));
        };
    }

过程宏的阶段见过程宏的执行阶段。

自定义语法

实测 · 本机执行输出
  编译退出码 0
  运行:
    [("a", 1), ("b", 5), ("c", 4)]
    空: 0

声明宏版本的写法见声明宏 macro_rules!。

编译期读文件的陷阱

实测 · 本机执行输出
  build 后运行: 版本 = "1.0"
  把 version.txt 改成 2.0 后 cargo build 输出: ['Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.01s']
  运行: 版本 = "1.0" ← 仍是旧值(宏没有重新运行)
  改动 main.rs(加一个换行)再 build 后运行: 版本 = "2.0"

错误报告

实测 · 本机执行输出
  error: 读取 不存在.txt 失败: No such file or directory (os error 2)
   --> src/main.rs:3:28
    |
  3 | fn main() { let _ = embed!("不存在.txt"); let _ = fib!(99999999999); let _ = fib!("x"); }
    |                            ^^^^^^^^^^^^
  error: number too large to fit in target type
   --> src/main.rs:3:53
    |
  3 | fn main() { let _ = embed!("不存在.txt"); let _ = fib!(99999999999); let _ = fib!("x"); }
    |                                                        ^^^^^^^^^^^
  error: expected integer literal
   --> src/main.rs:3:80
    |
  3 | fn main() { let _ = embed!("不存在.txt"); let _ = fib!(99999999999); let _ = fib!("x"); }
    |                                                                                   ^^^
  error: could not compile `app` (bin "app") due to 3 previous errors

对照 include_str!

实测 · 本机执行输出
  build 后运行: include_str 版本 = "3.0"
  改文件为 4.0 后 cargo build: ['Compiling app v0.1.0 (<实验目录>/proj/u82/app)', 'Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.12s']
  运行: include_str 版本 = "4.0" ← 新值,cargo 追踪了 include_str! 读的文件

想让自定义宏也在文件改动时重编译,可以尝试 build.rs 的 cargo:rerun-if-changed(见 Build Script build.rs),这一点没有验证。

分步演示

函数式宏
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。crates:syn 2、quote 1、proc-macro2 1
    • 没有验证:proc_macro::tracked_path 一类的不稳定 API、宏里读环境变量的追踪(tracked_env)、build.rs 配合 rerun-if-changed 是否能解决此陷阱
    京ICP备2026067502号-1京公网安备11010502063487号