Rust 工程化实战:用 macro_rules 写零成本宏消除样板代码

test12026-09-251 次阅读

宏解决什么问题

Rust 的泛型擅长类型层面的复用,但有些重复不是类型参数能消掉的:比如为多个结构体实现同一套 trait、构造一组同名测试、按固定模式打印日志。macro_rules! 是声明式宏,在编译期展开成代码,零运行期开销,且能跨多种语法结构复用。

第一个宏:自动实现 Debug 风格摘要

假设想给一批结构体快速加一个 summary 方法,返回字段名与值拼接的字符串:

macro_rules! impl_summary {
    ($name:ident { $($field:ident),* }) => {
        impl $name {
            pub fn summary(&self) -> String {
                let mut s = String::from(stringify!($name));
                $(
                    s.push_str(&format!(" {}={}", stringify!($field), self.$field));
                )*
                s
            }
        }
    };
}

struct User { id: u64, name: String }
struct Order { id: u64, amount: f64 }

impl_summary!(User { id, name });
impl_summary!(Order { id, amount });

变长参数宏:批量构造测试

下面这个宏给一组输入一次性生成测试用例,省掉逐个 fn 的样板:

macro_rules! test_cases {
    ($($name:ident: $input:expr => $expect:expr),* $(,)?) => {
        $(
            #[test]
            fn $name() {
                assert_eq!(process($input), $expect);
            }
        )*
    };
}

test_cases! {
    t_empty: "" => 0,
    t_single: "a" => 1,
    t_multi: "a b c" => 3,
}

调试宏展开

不确定宏生成了什么代码时,用编译器选项把它.dump 出来:

cargo expand   # 需要安装 cargo-expand
# 或
rustc -Zunpretty=expanded main.rs

使用准则

  • 宏的匹配臂从左到右尝试,越具体的规则放越前面,避免被宽泛规则抢先匹配。
  • 重复段 $(...) 的 separators 要写全,比如 $(,)? 允许结尾逗号,能让调用处更宽容。
  • 宏能隐藏控制流,可读性差于普通函数,仅在"确实跨类型/跨结构重复"时使用,别用来炫技。
  • hygiene(卫生性)让宏内生成的变量不会和调用处冲突,但跨宏传递标识符(ident)时要注意命名意图清晰。

macro_rules! 是 Rust 工程里削减样板的主力工具:编译期展开、零运行时成本、能覆盖泛型触及不到的语法级重复。用得好,既保住类型安全,又把重复代码压到最低。

T

test1

文章作者

宏解决什么问题 Rust 的泛型擅长类型层面的复用,但有些重复不是类型参数能消掉的:比如为多个结构体实现同一套 tra...

分类
技术
发布时间
2026-09-25
字数
约 1513 字
阅读
1 次