You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现Rust宏:将带add_enum属性的结构体自动加入枚举?

这个需求完全可行,实现思路如下

普通声明宏没法搞定这事——因为Rust宏是按代码顺序逐段展开的,后面的宏没法主动获取前面代码的属性信息。得用**过程宏(proc-macro)**配合syn和quote库来实现,核心思路是让枚举的过程宏扫描当前文件的所有代码项,筛选出带对应#[add_enum]属性的结构体,再生成枚举变体。

具体实现步骤

  1. 搭建proc-macro子 crate:
    给项目新增一个proc-macro类型的子 crate,依赖syn(用于解析Rust语法树)、quote(用于生成代码)和proc-macro2。

  2. 实现#[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()
    }
    
  3. 实现#[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(&current_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,提问作者ねこめハコ

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 14:50:15