proc_macro_attribute 详细教程
Rust 的 proc_macro_attribute
是过程宏系统中用于定义自定义属性宏的机制。它允许开发者在编译时修改或扩展代码项(如函数、结构体、模块等),通过接收属性参数和项 TokenStream,生成新的 TokenStream 来替换或增强原项。这是一种强大的元编程工具,可以用于代码注入、装饰器模式和语法增强。属性宏的签名是 #[proc_macro_attribute] pub fn name(attr: TokenStream, item: TokenStream) -> TokenStream
,其中 attr
是属性参数的 TokenStream,item
是被修饰项的 TokenStream,返回值是新的 TokenStream。
1. proc_macro_attribute 简介
属性宏用于装饰代码项,例如 #[my_attr]
fn f() {},宏可以修改 f 的定义、添加代码或生成新项。
- 优势:编译时执行,零运行时开销;支持任意 item 修改;可用于测试框架(如 #[test])、性能提示(如 #[inline])或自定义注解。
- 限制:只能用于属性位置;输入 item 必须有效 TokenStream;复杂宏增加编译时间。
- 性能考虑:宏执行时间取决于 Token 处理,简单宏 <1ms,复杂 syn parse 10-100ms;用 rustc -Z time-passes 测量。
- 跨平台:宏 compiler 侧,一致;lib 作为 DLL/so,测试加载。
- 测试:用 proc-macro-test crate 测试属性应用;cargo expand 查看展开代码。
- 常见用例:添加日志、计时函数、忽略警告、自定义链接。
- 替代:macro_rules! 属性有限;derive 只 struct/enum。
- 历史:Rust 1.15 引入,1.30 稳定;1.80+ 优化 Span 处理。
2. 设置环境
创建 proc-macro lib 项目。
Cargo.toml:
[package]
name = "my_attr_macro"
version = "0.1.0"
edition = "2021"
[lib]
proc-macro = true
[dependencies]
proc-macro2 = "1.0"
syn = { version = "2.0", features = ["full", "visit-mut", "extra-traits"] } // full for complete parsing, visit-mut for modifying AST, extra-traits for debugging
quote = "1.0"
darling = "0.20" // for attribute parsing
proc-macro-error = "1.0" // for user-friendly errors
thiserror = "1.0" // for custom errors
- 解释:proc-macro = true 启用过程宏库;syn features 启用完整解析/修改/调试;darling 简化属性解析;proc-macro-error 提供 attr 友好错误报告。
- 性能:syn full 增加编译时间 20-50%,但必要复杂宏;用 minimal features 优化简单宏。
- 陷阱:无 proc-macro = true,编译 Err "not a proc-macro crate";dep 版本不匹配导致 syn/quote 兼容问题。
- 优化:使用 proc-macro2 for stable API;避免 unnecessary syn features for lightweight macros。
- 测试设置:创建单独 test crate 依赖 my_attr_macro,use #[my_attr] in tests。
- 高级:add build.rs 生成宏代码 (meta-meta programming);use cargo-sync-readme 同步文档;enable nightly features like proc_macro_span for better error locations。
- 变体:for bin proc-macro tools, use [bin] but lib for macros。
3. 基本属性宏
属性宏修改 item,attr 是参数。
示例1: 简单包装函数
lib.rs
#![allow(unused)] fn main() { use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, ItemFn}; #[proc_macro_attribute] pub fn log_entry(attr: TokenStream, item: TokenStream) -> TokenStream { let fn_item = parse_macro_input!(item as ItemFn); let fn_name = &fn_item.sig.ident; let block = &fn_item.block; quote! { fn_item.sig { println!("Entering {}", stringify!(#fn_name)); block } }.into() } }
使用:
#![allow(unused)] fn main() { #[log_entry] fn my_function() { println!("Inside"); } }
- 解释:parse_macro_input! 解析 ItemFn;quote! 包装 block 添加 print;stringify! 转 fn_name 字符串。
- 性能:小 fn parse <1ms,quote O(n) Token。
- 陷阱:async fn 需要 quote async sig;attr 忽略。
- 优化:use quote_spanned fn_item.span() 保留位置 for better errors。
- 测试:test crate use #[log_entry] fn my_test() {}, run check "Entering my_test" output。
- 高级:use fn_item.vis 保留 visibility 如 pub。
- 变体:if fn_item.sig.asyncness.is_some() add async log。
- 分析:this macro adds logging without runtime overhead beyond println!;for production, use tracing crate integration。
示例2: 添加返回日志
Extend example1 to log exit.
lib.rs (extend)
#![allow(unused)] fn main() { quote! { fn_item.sig { println!("Entering {}", stringify!(#fn_name)); let result = { #block }; println!("Exiting {}", stringify!(#fn_name)); result } } }
- 解释:wrap block in let result to log exit and return。
- 性能:negligible added code。
- 陷阱:void fn no return, use if fn_item.sig.output is -> () no result。
- 优化:use syn::ReturnType match output。
- 测试:check output "Entering" and "Exiting"。
- 高级:add timing with Instant now/elapsed in generated code。
- 变体:for async, quote async and .await result if needed (but attribute on async fn requires care)。
示例3: 属性忽略特定函数
Use attr to conditional log.
lib.rs
#![allow(unused)] fn main() { use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, ItemFn, LitBool}; #[proc_macro_attribute] pub fn conditional_log(attr: TokenStream, item: TokenStream) -> TokenStream { let enable = if attr.is_empty() { true } else { let lit = parse_macro_input!(attr as LitBool); lit.value }; let fn_item = parse_macro_input!(item as ItemFn); let fn_name = &fn_item.sig.ident; let block = &fn_item.block; if enable { quote! { fn_item.sig { println!("Enter {}", stringify!(#fn_name)); block } }.into() } else { item } } }
使用:
#![allow(unused)] fn main() { #[conditional_log(false)] fn no_log() {} }
- 解释:parse attr as LitBool;if false return original item。
- 性能:empty attr fast parse。
- 陷阱:attr not bool parse Err。
- 优化:use darling for robust attr parse。
- 测试:true/false attr check log presence。
- 高级:use Meta for key=value attr like #[log(enable = true)]。
- 变体:attr as ident for levels like "debug"。
示例4: 修改结构体添加字段
lib.rs
#![allow(unused)] fn main() { use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, ItemStruct, FieldsNamed}; #[proc_macro_attribute] pub fn add_id(attr: TokenStream, item: TokenStream) -> TokenStream { let mut item_struct = parse_macro_input!(item as ItemStruct); if let syn::Fields::Named(FieldsNamed { named: ref mut fields, .. }) = item_struct.fields { fields.push(parse_quote! { id: u32 }); } else { return quote! { compile_error!("Only named fields"); }.into(); } quote! { #item_struct }.into() } }
使用:
#![allow(unused)] fn main() { #[add_id] struct MyStruct { name: String, } // 展开 struct MyStruct { name: String, id: u32 } }
- 解释:parse ItemStruct;push field 添加 id。
- 性能:小 struct fast。
- 陷阱:unnamed/unit fields match Err。
- 优化:quote #item_struct 修改后。
- 测试:expand check added field。
- 高级:use visit_mut 修改 AST deeper。
- 变体:for enum add variant。
示例5: 添加方法到 trait
For trait item, add method.
lib.rs
#![allow(unused)] fn main() { use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, ItemTrait, TraitItem, TraitItemMethod}; #[proc_macro_attribute] pub fn add_trait_method(attr: TokenStream, item: TokenStream) -> TokenStream { let mut item_trait = parse_macro_input!(item as ItemTrait); item_trait.items.push(TraitItem::Method(TraitItemMethod { attrs: vec![], sig: parse_quote! { fn added(&self); }, default: None, semi_token: Some(syn::token::Semi { spans: [proc_macro::Span::call_site()] }), })); quote! { #item_trait }.into() } }
使用:
#![allow(unused)] fn main() { #[add_trait_method] trait MyTrait { fn existing(&self); } // 展开 trait MyTrait { fn existing(&self); fn added(&self); } }
- 解释:parse ItemTrait;push TraitItemMethod 添加方法。
- 性能:小 trait fast。
- 陷阱:default None for sig only。
- 优化:use parse_quote! 方便。
- 测试:expand check added method。
- 高级:use attr 指定 method name/sig。
- 变体:for impl add body。
示例6: 包装模块添加 use
For module item, add use.
lib.rs
#![allow(unused)] fn main() { use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, ItemMod, Item, UseTree}; #[proc_macro_attribute] pub fn add_use(attr: TokenStream, item: TokenStream) -> TokenStream { let mut item_mod = parse_macro_input!(item as ItemMod); if let Some((_, ref mut content)) = item_mod.content { content.insert(0, Item::Use(syn::ItemUse { attrs: vec![], vis: syn::Visibility::Inherited, use_token: syn::token::Use { span: proc_macro::Span::call_site() }, leading_colon: None, tree: UseTree::Path(syn::UsePath { ident: syn::Ident::new("std", proc_macro::Span::call_site()), colon2_token: syn::token::Colon2 { spans: [proc_macro::Span::call_site()] }, tree: Box::new(UseTree::Name(syn::UseName { ident: syn::Ident::new("collections", proc_macro::Span::call_site()), })), }), semi_token: syn::token::Semi { spans: [proc_macro::Span::call_site()] }, })); } quote! { #item_mod }.into() } }
使用:
#![allow(unused)] fn main() { #[add_use] mod my_mod { // 添加 use std::collections; } }
- 解释:parse ItemMod;insert Item::Use 添加 use。
- 性能:小 mod fast。
- 陷阱:no content (extern mod) Err。
- 优化:parse_quote! Item::Use。
- 测试:expand check added use。
- 高级:use attr 指定 use path。
- 变体:for crate root 添加 use。
示例7: 函数参数添加默认
Modify fn sig add default.
lib.rs
#![allow(unused)] fn main() { use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, ItemFn, FnArg, PatType}; #[proc_macro_attribute] pub fn default_arg(attr: TokenStream, item: TokenStream) -> TokenStream { let mut fn_item = parse_macro_input!(item as ItemFn); for arg in fn_item.sig.inputs.iter_mut() { if let FnArg::Typed(PatType { ty, .. }) = arg { if **ty == parse_quote! { i32 } { // 添加默认 = 0 } } } quote! { #fn_item }.into() } }
- 解释:iter_mut sig.inputs 修改 arg 添加 default (syn support default 1.39+)。
- 性能:小 sig fast。
- 陷阱:default 需 ty 支持。
- 优化:use visit_mut 修改。
- 测试:expand check default。
- 高级:attr 指定 which arg default value。
- 变体:for method self arg。
示例8: 结构体实现 trait
Add impl for struct.
lib.rs
#![allow(unused)] fn main() { use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, ItemStruct}; #[proc_macro_attribute] pub fn impl_trait(attr: TokenStream, item: TokenStream) -> TokenStream { let item_struct = parse_macro_input!(item as ItemStruct); let name = item_struct.ident; let impl_code = quote! { impl MyTrait for #name { fn method(&self) {} } }; quote! { item_struct impl_code }.into() } }
使用:
#![allow(unused)] fn main() { #[impl_trait] struct MyStruct; }
- 解释:生成 #item_struct + impl。
- 性能:fast。
- 陷阱:duplicate impl Err,用 if not exist。
- 优化:append to item。
- 测试:check impl method。
- 高级:use attr 指定 trait name。
- 变体:for enum 生成 impl。
示例9: 模块添加 item
Add const to mod.
lib.rs
#![allow(unused)] fn main() { use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, ItemMod}; #[proc_macro_attribute] pub fn add_const(attr: TokenStream, item: TokenStream) -> TokenStream { let mut item_mod = parse_macro_input!(item as ItemMod); if let Some((_, ref mut content)) = item_mod.content { content.push(parse_quote! { const ADDED: i32 = 42; }); } quote! { #item_mod }.into() } }
使用:
#![allow(unused)] fn main() { #[add_const] mod my_mod { // 添加 const ADDED = 42; } }
- 解释:push parse_quote! Item。
- 性能:fast。
- 陷阱:no content extern mod。
- 优化:use if content.is_some()。
- 测试:expand check const。
- 高级:use attr 指定 const value。
- 变体:add fn 或 struct。
示例10: trait 添加 method
lib.rs
#![allow(unused)] fn main() { use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, ItemTrait, TraitItem, TraitItemMethod}; #[proc_macro_attribute] pub fn add_method(attr: TokenStream, item: TokenStream) -> TokenStream { let mut item_trait = parse_macro_input!(item as ItemTrait); item_trait.items.push(TraitItem::Method(parse_quote! { fn added(&self); })); quote! { #item_trait }.into() } }
使用:
#![allow(unused)] fn main() { #[add_method] trait MyTrait { fn existing(&self); } // 展开 trait MyTrait { fn existing(&self); fn added(&self); } }
- 解释:push TraitItemMethod。
- 性能:fast。
- 陷阱:semi_token 需要。
- 优化:parse_quote! 方便。
- 测试:expand check added method。
- 高级:use attr 指定 method sig。
- 变体:add associated type/const。
示例11: 处理模块内 item
Use visit to modify inner items.
lib.rs
#![allow(unused)] fn main() { use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, ItemMod, visit_mut::VisitMut}; struct AddPrint; impl VisitMut for AddPrint { fn visit_item_fn_mut(&mut self, i: &mut syn::ItemFn) { i.block.stmts.insert(0, parse_quote! { println!("added"); }); } } #[proc_macro_attribute] pub fn add_print_to_fns(attr: TokenStream, item: TokenStream) -> TokenStream { let mut item_mod = parse_macro_input!(item as ItemMod); if let Some((_, ref mut content)) = item_mod.content { let mut visitor = AddPrint; for item in content.iter_mut() { visitor.visit_item_mut(item); } } quote! { #item_mod }.into() } }
使用:
#![allow(unused)] fn main() { #[add_print_to_fns] mod my_mod { fn f1() {} fn f2() {} } // 展开 fn f1 { println!("added"); } fn f2 { println!("added"); } }
- 解释:VisitMut 修改 mod 内 fn 添加 stmt。
- 性能:mod 大 visit 慢。
- 陷阱:visit_mut 需要 features ["visit-mut"]。
- 优化:针对 fn visit_item_fn_mut。
- 测试:expand check added print。
- 高级:递归 visit mod 内 mod。
- 变体:add to struct method。
示例12: attr 多参数
Use punctuated parse attr.
lib.rs
#![allow(unused)] fn main() { use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, ItemFn, punctuated::Punctuated, Token, LitStr}; #[proc_macro_attribute] pub fn multi_attr(attr: TokenStream, item: TokenStream) -> TokenStream { let args: Punctuated<LitStr, Token![,]> = Punctuated::parse_terminated.parse(attr).unwrap(); let messages = args.iter().map(|lit| lit.value()).collect::<Vec<_>>(); let fn_item = parse_macro_input!(item as ItemFn); let block = &fn_item.block; let prints = messages.iter().map(|msg| quote! { println!("{}", #msg); }); quote! { fn_item.sig { (#prints)* block } }.into() } }
使用:
#![allow(unused)] fn main() { #[multi_attr("msg1", "msg2")] fn my_fn() {} }
- 解释:Punctuated parse comma sep LitStr。
- 性能:小 args 快。
- 陷阱:无 comma 或非 str Err。
- 优化:use darling FromMeta 多类型。
- 测试:不同 args 测试 prints。
- 高级:use MetaList for nested (key = val)。
- 变体:attr as expr 用 parse::Parser。
示例13: 修改 trait impl
For impl item, add method impl.
lib.rs
#![allow(unused)] fn main() { use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, ItemImpl, ImplItem, ImplItemMethod}; #[proc_macro_attribute] pub fn add_impl_method(attr: TokenStream, item: TokenStream) -> TokenStream { let mut item_impl = parse_macro_input!(item as ItemImpl); item_impl.items.push(ImplItem::Method(parse_quote! { fn added(&self) { } })); quote! { #item_impl }.into() } }
使用:
#![allow(unused)] fn main() { #[add_impl_method] impl MyTrait for MyStruct { fn existing(&self) {} } // 展开 impl MyTrait for MyStruct { fn existing(&self) {} fn added(&self) {} } }
- 解释:push ImplItemMethod 添加方法。
- 性能:fast。
- 陷阱:sig 匹配 trait。
- 优化:parse_quote!。
- 测试:expand check added method。
- 高级:use attr 指定 body。
- 变体:for trait def add sig。
示例14: 删除 item
Return empty TokenStream delete item.
lib.rs
#![allow(unused)] fn main() { use proc_macro::TokenStream; #[proc_macro_attribute] pub fn remove(attr: TokenStream, item: TokenStream) -> TokenStream { TokenStream::new() // 删除 item } }
使用:
#![allow(unused)] fn main() { #[remove] fn removed() {} // 展开为空,移除 fn }
- 解释:空返回删除。
- 性能:fast。
- 陷阱:删除必要 item Err。
- 优化:条件删除。
- 测试:expand check no fn。
- 高级:use if attr "true" 删除。
- 变体:return modified or empty。
示例15: 注入全局代码
Add const outside item.
lib.rs
#![allow(unused)] fn main() { use proc_macro::TokenStream; use quote::quote; #[proc_macro_attribute] pub fn inject_const(attr: TokenStream, item: TokenStream) -> TokenStream { quote! { const INJECTED: i32 = 42; item }.into() } }
使用:
#![allow(unused)] fn main() { #[inject_const] mod my_mod {} // 展开 const INJECTED = 42; mod my_mod {} }
- 解释:quote 添加 const + #item。
- 性能:fast。
- 陷阱:duplicate const Err。
- 优化:check if exist (no, compile time no)。
- 测试:expand check const。
- 高级:inject use 或 extern。
- 变体:inject in mod if ItemMod。
示例16: visit_mut 修改 block
Use visit_mut add stmt to fn block.
lib.rs
#![allow(unused)] fn main() { use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, ItemFn, visit_mut::VisitMut}; struct AddStmt; impl VisitMut for AddStmt { fn visit_block_mut(&mut self, b: &mut syn::Block) { b.stmts.insert(0, parse_quote! { println!("added"); }); } } #[proc_macro_attribute] pub fn add_stmt(attr: TokenStream, item: TokenStream) -> TokenStream { let mut fn_item = parse_macro_input!(item as ItemFn); let mut visitor = AddStmt; visitor.visit_item_fn_mut(&mut fn_item); quote! { #fn_item }.into() } }
使用:
#![allow(unused)] fn main() { #[add_stmt] fn my_fn() { // 添加 println!("added"); } }
- 解释:VisitMut 修改 block insert stmt。
- 性能:block 大 visit 慢。
- 陷阱:features ["visit-mut"] 需要。
- 优化:针对 block visit_block_mut。
- 测试:expand check added stmt。
- 高级:递归 visit all nested block。
- 变体:add to struct init block。
示例17: 处理 trait
Add default method to trait.
lib.rs
#![allow(unused)] fn main() { use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, ItemTrait, TraitItem, TraitItemMethod}; #[proc_macro_attribute] pub fn default_method(attr: TokenStream, item: TokenStream) -> TokenStream { let mut item_trait = parse_macro_input!(item as ItemTrait); item_trait.items.push(TraitItem::Method(TraitItemMethod { attrs: vec![], sig: parse_quote! { fn default_method(&self) { println!("default"); } }, default: Some(parse_quote! { { println!("default"); } }), semi_token: None, })); quote! { #item_trait }.into() } }
使用:
#![allow(unused)] fn main() { #[default_method] trait MyTrait {} // 展开 trait MyTrait { fn default_method(&self) { println!("default"); } } }
- 解释:push TraitItemMethod with default block。
- 性能:fast。
- 陷阱:default Some for body。
- 优化:parse_quote!。
- 测试:expand check default method。
- 高级:use attr 指定 body。
- 变体:add to impl default impl。
示例18: 模块级注入
Inject item to mod.
lib.rs
#![allow(unused)] fn main() { use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, ItemMod}; #[proc_macro_attribute] pub fn inject_item(attr: TokenStream, item: TokenStream) -> TokenStream { let mut item_mod = parse_macro_input!(item as ItemMod); if let Some((_, ref mut content)) = item_mod.content { content.push(parse_quote! { fn injected() { } }); } else { // extern mod, append after return quote! { #item_mod fn injected() { } }.into(); } quote! { #item_mod }.into() } }
使用:
#![allow(unused)] fn main() { #[inject_item] mod my_mod {} // 展开 mod my_mod { fn injected() { } } }
- 解释:push to content or append。
- 性能:fast。
- 陷阱:extern mod no content。
- 优化:parse_quote! Item。
- 测试:expand check injected。
- 高级:inject use 或 const。
- 变体:inject to crate root。
示例19: 泛型 item 处理
For generic fn, preserve generics.
lib.rs
#![allow(unused)] fn main() { use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, ItemFn}; #[proc_macro_attribute] pub fn gen_attr(attr: TokenStream, item: TokenStream) -> TokenStream { let fn_item = parse_macro_input!(item as ItemFn); let generics = &fn_item.generics; let (impl_gen, ty_gen, where_clause) = generics.split_for_impl(); // 生成 quote! { #fn_item }.into() } }
- 解释:split_for_impl 保留 generic。
- 性能:fast。
- 陷阱:generic params 处理。
- 优化:quote #impl_gen 等。
- 测试:generic fn 测试展开。
- 高级:add bound to where_clause。
- 变体:for struct generic。
示例20: visit 修改 nested
Use visit_mut modify inner expr.
lib.rs
#![allow(unused)] fn main() { use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, ItemFn, visit_mut::VisitMut, Expr}; struct ReplaceLit; impl VisitMut for ReplaceLit { fn visit_expr_mut(&mut self, e: &mut syn::Expr) { if let Expr::Lit(lit) = e { if let syn::Lit::Int(int) = &lit.lit { if int.base10_parse::<i32>().unwrap() == 42 { *e = parse_quote! { 43 }; } } } } } #[proc_macro_attribute] pub fn replace_42(attr: TokenStream, item: TokenStream) -> TokenStream { let mut fn_item = parse_macro_input!(item as ItemFn); let mut visitor = ReplaceLit; visitor.visit_item_fn_mut(&mut fn_item); quote! { #fn_item }.into() } }
使用:
#![allow(unused)] fn main() { #[replace_42] fn my_fn() -> i32 { 42 } }
展开: fn my_fn() -> i32 { 43 }
- 解释:VisitMut 修改 expr lit 42 to 43。
- 性能:expr 多 visit 慢。
- 陷阱:features ["visit-mut"] 需要。
- 优化:针对 expr visit_expr_mut。
- 测试:expand check replaced。
- 高级:递归 visit all nested expr。
- 变体:replace ident 或 type。
9. 总结
proc_macro_attribute 是强大装饰工具,结合 syn/quote/darling 高效开发。20 示例覆盖基础到高级,练习应用。