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

如何用Rust派生过程宏检查结构体是否派生其他Trait?

实现条件性为Builder派生Debug的解决方案

要实现仅当原结构体派生Debug时,自动为生成的Builder类型实现Debug,核心是在proc宏中解析原结构体的#[derive(...)]属性,判断是否包含Debug,再条件性生成代码。以下是具体实现步骤和代码:

核心思路

  1. 解析输入的DeriveInput,遍历其属性列表找到#[derive(...)]元数据
  2. 检查该元数据中是否包含Debug标识
  3. 根据检查结果,决定是否为生成的Builder结构体添加#[derive(Debug)]

完整proc宏实现

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput, FieldsNamed, Ident};

#[proc_macro_derive(Builder)]
pub fn derive_builder(input: TokenStream) -> TokenStream {
    // 解析输入的结构体定义
    let input = parse_macro_input!(input as DeriveInput);
    let struct_name = input.ident;
    let builder_name = format!("{}Builder", struct_name);
    let builder_ident = Ident::new(&builder_name, struct_name.span());

    // 检查原结构体是否派生了Debug
    let has_debug = input.attrs.iter()
        .filter_map(|attr| attr.parse_meta().ok())
        .any(|meta| {
            // 匹配derive属性列表,并检查是否包含Debug
            matches!(meta, syn::Meta::List(list) 
                if list.path.is_ident("derive") && 
                list.nested.iter().any(|nested| {
                    matches!(nested, syn::NestedMeta::Meta(syn::Meta::Path(path)) 
                        if path.is_ident("Debug"))
                })
            )
        });

    // 条件生成Debug派生宏
    let debug_derive = if has_debug {
        quote!(#[derive(Debug)])
    } else {
        quote!()
    };

    // 提取原结构体的命名字段(仅支持命名字段结构体)
    let fields = match input.data {
        syn::Data::Struct(syn::DataStruct { fields: syn::Fields::Named(fields), .. }) => fields,
        _ => panic!("Builder仅支持为命名字段的结构体派生"),
    };

    // 生成Builder结构体的字段(转为Option类型)
    let builder_fields = fields.named.iter().map(|field| {
        let ident = &field.ident;
        let ty = &field.ty;
        quote! { #ident: Option<#ty>, }
    });

    // 生成字段设置方法和build方法
    let builder_methods = fields.named.iter().map(|field| {
        let ident = &field.ident;
        let ty = &field.ty;
        quote! {
            pub fn #ident(mut self, value: #ty) -> Self {
                self.#ident = Some(value);
                self
            }
        }
    });
    let build_fields = fields.named.iter().map(|field| {
        let ident = &field.ident;
        quote! { #ident: self.#ident.expect(concat!(stringify!(#ident), " 必须被初始化")), }
    });

    // 组装最终生成的代码
    let output = quote! {
        #debug_derive
        pub struct #builder_ident {
            #(#builder_fields)*
        }

        impl #struct_name {
            pub fn builder() -> #builder_ident {
                #builder_ident {
                    #(#builder_fields)*
                }
            }
        }

        impl #builder_ident {
            #(#builder_methods)*

            pub fn build(self) -> #struct_name {
                #struct_name {
                    #(#build_fields)*
                }
            }
        }
    };

    output.into()
}

关键细节说明

  • 解析#[derive(...)]属性时,需要将属性转为Meta::List类型,再遍历其内部的嵌套元数据,判断是否存在Debug路径标识
  • 仅支持命名字段的结构体,若需要支持其他字段类型(如元组结构体),可扩展fields的匹配逻辑
  • 生成Builder字段时使用Option<T>,确保未设置的字段能在build阶段触发 panic 提示

测试验证

对应你给出的示例:

  1. 当结构体派生Debug时:
#[derive(Debug, Builder)]
struct Foo { bar: String }

fn main() {
    println!("{:?}", Foo::builder()); // 正常编译运行,FooBuilder已实现Debug
}
  1. 当结构体未派生Debug时:
#[derive(Builder)]
struct Foo { bar: String }

fn main() {
    println!("{:?}", Foo::builder()); // 编译报错,FooBuilder未实现Debug
}

内容的提问来源于stack exchange,提问作者Typhaon

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 01:15:09