如何实现Rust宏:将带add_enum属性的结构体自动加入枚举?
这个需求完全可行,实现思路如下
普通声明宏没法搞定这事——因为Rust宏是按代码顺序逐段展开的,后面的宏没法主动获取前面代码的属性信息。得用**过程宏(proc-macro)**配合syn和quote库来实现,核心思路是让枚举的过程宏扫描当前文件的所有代码项,筛选出带对应#[add_enum]属性的结构体,再生成枚举变体。
具体实现步骤
搭建proc-macro子 crate:
给项目新增一个proc-macro类型的子 crate,依赖syn(用于解析Rust语法树)、quote(用于生成代码)和proc-macro2。实现#[add_enum]属性宏:
这个宏无需复杂逻辑,保留原结构体代码即可,也可以给结构体加个隐藏标记方便后续识别:#[proc_macro_attribute] pub fn add_enum(args: TokenStream, input: TokenStream) -> TokenStream { // 解析传入的枚举名称参数 let enum_name = syn::parse_macro_input!(args as syn::Ident); let struct_item = syn::parse_macro_input!(input as syn::ItemStruct); quote::quote! { #struct_item // 生成空trait实现作为标记,方便后续查找 impl crate::internal::AutoEnumMember<#enum_name> for #struct_item {} }.into() }实现#[auto_enum]属性宏(核心):
这个宏要读取当前文件的完整代码,找出所有带#[add_enum(目标枚举名)]的结构体,再生成对应的枚举变体:#[proc_macro_attribute] pub fn auto_enum(_args: TokenStream, input: TokenStream) -> TokenStream { let enum_item = syn::parse_macro_input!(input as syn::ItemEnum); let enum_name = &enum_item.ident; let enum_vis = &enum_item.vis; // 读取当前文件内容(可通过syn的Span特性优化路径获取,避免硬编码) let file_path = std::env::var("CARGO_MANIFEST_DIR").unwrap() + "/src/main.rs"; let current_file = std::fs::read_to_string(file_path).unwrap(); let parsed_file = syn::parse_file(¤t_file).unwrap(); // 筛选符合条件的结构体,生成枚举变体代码 let variants = parsed_file.items.iter() .filter_map(|item| { if let syn::Item::Struct(s) = item { // 检查结构体是否带有匹配的#[add_enum]属性 for attr in &s.attrs { if let Ok(syn::Meta::List(meta)) = attr.parse_meta() { if meta.path.is_ident("add_enum") && meta.nested.len() == 1 { if let syn::NestedMeta::Meta(syn::Meta::Path(p)) = &meta.nested[0] { if p.is_ident(enum_name) { let struct_name = &s.ident; return Some(quote::quote! { #struct_name(#struct_name), }); } } } } } } None }); // 输出最终的枚举代码 quote::quote! { #enum_vis enum #enum_name { #(#variants)* } }.into() }
注意事项
- 读取文件路径时建议用
syn的Span::source_file()(需启用syn的full特性)自动获取当前文件路径,避免硬编码路径适配问题。 - 示例中用空trait做标记,也可以直接解析结构体的
#[add_enum]属性完成筛选,两种方式均可。 - 过程宏必须放在单独的proc-macro crate中实现,主 crate 引入后才能正常使用。
使用示例
主 crate 代码和你给出的示例完全一致:
use your_proc_macro_crate::{add_enum, auto_enum}; #[add_enum(AutoEnum)] struct A(usize); #[add_enum(AutoEnum)] struct B(i32); #[auto_enum] enum AutoEnum {}
展开后就能得到你期望的代码:
struct A(usize); struct B(i32); enum AutoEnum { A(A), B(B), }
内容的提问来源于stack exchange,提问作者ねこめハコ
相关产品推荐
相关产品推荐

