你有没有写过这样的ref="/tag/131/" style="color:#C468A7;font-weight:bold;">代码:重复定义一堆结构体,字段名一样、类型一样,就差一个后缀?比如 UserV1、UserV2、UserV3……改一个字段,得手动同步三遍。或者日志里总要写 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 更少。