结论:macro_rules! 是模式匹配的文本替换宏——定义和使用可以在同一 crate,写起来像 match 分支,适合语法样板消除(vec!、println! 这类);过程宏操作 TokenStream,是编译期运行的 Rust 函数,能做任意代码变换,分 derive 宏、属性宏、函数式宏三种,必须放在独立的 proc-macro crate,适合 #[derive(Serialize)]、#[tokio::main] 这类深度代码生成。
展开:选型标准:能用声明宏表达(模式清晰、分支有限)就不用过程宏——过程宏引入 syn/quote 依赖、拖慢编译、错误信息更难读。声明宏的局限也正是过程宏的存在理由:无法检查标识符合法性之外的语义、不能做递归结构遍历、无法基于类型信息生成代码(derive 需要读 struct 字段)。卫生性差异:macro_rules! 部分卫生(局部变量不泄漏),过程宏完全无卫生,要小心命名冲突(用 __ 前缀)。调试手段:cargo expand 看两者展开结果。
macro_rules! hashmap {
($($k:expr => $v:expr),* $(,)?) => {{
let mut m = std::collections::HashMap::new();
$(m.insert($k, $v);)*
m
}};
}
let m = hashmap!["a" => 1, "b" => 2];
易错点:声明宏的递归展开有 recursion_limit;过程宏 panic 会让整个编译失败并给出难看的信息,要用 syn::Error 回报错误。追问方向:derive 宏的辅助属性(#[serde(rename)])注册机制、trybuild 测编译失败用例。