Rust编程宏教程:从写死的代码到会变形的函数

你有没有写过这样的ref="/tag/131/" style="color:#C468A7;font-weight:bold;">代码:重复定义一堆结构体,字段名一样、类型一样,就差一个后缀?比如 UserV1UserV2UserV3……改一个字段,得手动同步三遍。或者日志里总要写 println!("[{}] {}", line!(), "处理完成");,每次复制粘贴还容易漏掉 line!?这时候,Rust 的宏不是炫技工具,是真能帮你省下咖啡续命时间的活儿。

宏不是函数,是代码生成器

别被“macro”吓住——它不运行,也不编译时求值,而是在编译前把你的标记(token)按规则“拼”成新代码。就像有个小抄写员,你给它模板和参数,它唰唰替你写出一整段 Rust 代码,再交给编译器处理。

先看一个最常用的:println!

你以为它是个函数?其实它是宏:

println!("Hello, {}!", name); // ✅ 正常用
println!("User id: {}, status: {}", id, status); // ✅ 参数个数自由
// println!("{} {} {} {} {}", a,b,c,d,e,f); // ❌ 编译报错:格式串参数不够

它能检查参数数量是否匹配格式串,还能在编译期展开成带文件名、行号的完整输出调用——普通函数根本做不到这点。

动手写一个自己的宏:debug_log!

假设你调试时总想快速打印变量名+值,比如 debug_log!(count); 应该输出 count = 42。手写?太慢。用宏:

macro_rules! debug_log {
    ($x:expr) => {{
        let val = $x;
        println!("{} = {:?}", stringify!($x), val);
    }};
}

试试效果:

let count = 42;
debug_log!(count); // 输出:count = 42

这里 stringify!($x) 把标识符原样转成字符串,$x:expr 表示接受任意表达式——它甚至能接 vec![1,2,3].len(),自动展开成 vec![1,2,3].len() = 3

进阶:属性宏——给结构体自动加方法

你写了个 User 结构体,每次都要手动实现 to_json?来个 #[derive(Jsonify)] 吧:

use proc_macro::TokenStream;

#[proc_macro_derive(Jsonify)]
pub fn derive_jsonify(input: TokenStream) -> TokenStream {
    let ast = syn::parse(input).unwrap();
    let gen = impl_jsonify(&ast);
    gen.into()
}

(实际实现略长,但核心就这三行逻辑)之后你只需:

#[derive(Jsonify)]
struct User {
    name: String,
    age: u8,
}

编译时,宏就自动给你生成 impl User { fn to_json(&self) -> String { ... } }。不需要依赖外部 crate,自己写的宏,可控又轻量。

小提醒:别滥用,也别怕用

宏不是银弹。如果三行代码就能搞定,别硬套宏;但如果重复模式出现 3 次以上,尤其涉及类型、字段名、模块路径这些编译期才确定的东西——宏就是为你准备的。写 Rust 不是比谁语法熟,而是比谁让机器干的活更多、自己盯的 bug 更少。