Rust Builder 派生宏实现说明

需求

编写宏为结构体生成构建器模式,具备以下特性:

  • 构建器中的可选字段
  • 每个字段的 setter 方法(返回 self 支持链式调用)
  • StringVec<String> 类型的特殊处理
  • build() 方法返回 Result,包含缺失字段的错误信息

实现方法

健壮和灵活的 Builder 派生宏实现:

use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote, ToTokens};
use syn::{
    parse_macro_input, parse_quote, Attribute, Data, DataStruct, DeriveInput, Fields, GenericParam,
    Generics, Ident, Type, Visibility,
};

#[proc_macro_derive(Builder, attributes(builder))]
pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let ast = parse_macro_input!(input as DeriveInput);
    impl_builder_macro(&ast).into()
}

fn impl_builder_macro(ast: &DeriveInput) -> TokenStream {
    // 提取结构体名称、可见性和泛型参数
    let name = &ast.ident;
    let vis = &ast.vis;
    let generics = &ast.generics;
    
    // 创建无约束的泛型版本(用于 impl 块)
    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
    
    // 构建器名称(如 StructNameBuilder)
    let builder_name = format_ident!("{}Builder", name);
    
    // 提取结构体字段(仅支持具名结构体)
    let fields = match &ast.data {
        Data::Struct(DataStruct {
            fields: Fields::Named(fields),
            ..
        }) => &fields.named,
        _ => panic!("Builder 仅支持具名结构体"),
    };

    // 生成构建器结构体的字段定义
    let builder_fields = fields.iter().map(|f| {
        let name = &f.ident;
        let ty = &f.ty;
        let vis = &f.vis;
        
        // 检查 builder 属性(判断是否为可选字段)
        let is_required = !has_builder_attr(f, "optional");
        
        // 统一使用 Option 包装字段,required 字段在构建时检查是否为 None
        quote! { #vis #name: Option<#ty> }
    });
    
    // 构建器结构体定义(包含 Default 派生)
    let builder_struct = quote! {
        #[derive(Default)]
        #vis struct #builder_name #ty_generics #where_clause {
            #(#builder_fields,)*
        }
    };
    
    // 生成字段 setter 方法(根据类型自动适配)
    let setter_methods = fields.iter().map(|f| {
        let name = &f.ident;
        let ty = &f.ty;
        let vis = &f.vis;
        
        generate_setter_method(name, ty, vis)
    });
    
    // 生成 build 方法的字段初始化逻辑
    let build_field_initializers = fields.iter().map(|f| {
        let name = &f.ident;
        let name_str = name.as_ref().unwrap().to_string();
        
        // 判断字段是否为可选(通过 #[builder(optional)] 标记)
        let is_optional = has_builder_attr(f, "optional");
        
        if is_optional {
            // 可选字段:直接传递 Option 值(可为 None)
            quote! { #name: self.#name.clone() }
        } else {
            // 必需字段:检查是否为 None,否则返回错误
            quote! { 
                #name: self.#name.clone().ok_or_else(|| 
                    format!("字段‘{}’是必需的,但未提供", #name_str)
                )?
            }
        }
    });
    
    // 构建器方法实现(包括 new、setter、build)
    let builder_impl = quote! {
        impl #impl_generics #builder_name #ty_generics #where_clause {
            /// 创建一个所有字段为 None 的新构建器(默认实现)
            #vis fn new() -> Self {
                Self::default()
            }
            
            // 生成 setter 方法(根据类型自动适配)
            #(#setter_methods)*
            
            /// 构建最终结构体,缺失必需字段时返回错误
            #vis fn build(self) -> Result<#name #ty_generics, String> {
                Ok(#name {
                    #(#build_field_initializers,)*
                })
            }
        }
    };
    
    // 在原始结构体上实现 builder() 方法,方便创建构建器
    let struct_impl = quote! {
        impl #impl_generics #name #ty_generics #where_clause {
            /// 创建该结构体的构建器
            #vis fn builder() -> #builder_name #ty_generics {
                #builder_name::new()
            }
        }
    };

    // 组合所有生成的代码
    quote! {
        #builder_struct
        #builder_impl
        #struct_impl
    }
}

/// 根据字段类型生成对应的 setter 方法(支持 String、Vec<String>、Vec<T>、Option<T> 等)
fn generate_setter_method(name: &Option<Ident>, ty: &Type, vis: &Visibility) -> TokenStream {
    let name = name.as_ref().unwrap();
    let type_str = quote!(#ty).to_string();

    if type_str.contains("String") {
        if type_str.contains("Vec") {
            // 处理 Vec<String>:支持任何可迭代的字符串类型(如 &str)
            quote! {
                /// 设置字段为任意可迭代的字符串类型(自动转换为 String)
                #vis fn #name<I, S>(mut self, value: I) -> Self 
                where
                    I: IntoIterator<Item = S>,
                    S: AsRef<str>,
                {
                    self.#name = Some(value.into_iter().map(|s| s.as_ref().to_string()).collect());
                    self
                }
            }
        } else {
            // 处理 String:支持任何字符串类型(如 &str)
            quote! {
                /// 设置字段为任意字符串类型(自动转换为 String)
                #vis fn #name<S: AsRef<str>>(mut self, value: S) -> Self {
                    self.#name = Some(value.as_ref().to_string());
                    self
                }
            }
        }
    } else if type_str.contains("Vec") {
        // 处理其他 Vec<T>:支持任何可迭代的 T 类型
        quote! {
            /// 设置字段为任意可迭代的兼容类型(自动收集为 Vec)
            #vis fn #name<I>(mut self, value: I) -> Self 
            where
                I: IntoIterator<Item = <#ty as IntoIterator>::Item>,
            {
                self.#name = Some(value.into_iter().collect());
                self
            }
        }
    } else if type_str.contains("Option") {
        // 处理 Option<T>:直接设置值(保留 Option 语义)
        quote! {
            /// 设置字段为一个可选值
            #vis fn #name(mut self, value: #ty) -> Self {
                self.#name = Some(value);
                self
            }
        }
    } else {
        // 通用情况:直接设置原始类型
        quote! {
            /// 设置字段值
            #vis fn #name(mut self, value: #ty) -> Self {
                self.#name = Some(value);
                self
            }
        }
    }
}

/// 检查字段是否包含指定的 builder 属性(如 #[builder(optional)])
fn has_builder_attr(field: &syn::Field, attr_name: &str) -> bool {
    field.attrs.iter().any(|attr| {
        if let Ok(meta) = attr.parse_meta() {
            if meta.path().is_ident("builder") {
                if let syn::Meta::List(list) = meta {
                    list.nested.iter().any(|nested_meta| {
                        if let syn::NestedMeta::Meta(syn::Meta::Path(path)) = nested_meta {
                            path.is_ident(attr_name)
                        } else {
                            false
                        }
                    })
                } else {
                    false
                }
            } else {
                false
            }
        } else {
            false
        }
    })
}

关键点

  1. 泛型支持:改进后的宏正确处理原始结构体中的泛型类型和约束。
  2. 属性自定义:通过 #[builder(...)] 属性定制行为:
    • #[builder(optional)] 标记可选字段(构建时允许为 None)。
  3. 类型处理增强
    • 更健壮地处理 StringVec<String> 和通用 Vec<T>Option<T> 类型。
  4. 便捷方法
    • 原始结构体新增 builder() 方法,直接创建构建器。
    • 构建器自动派生 Default,支持默认初始化。
  5. 文档注释:每个生成的方法包含说明文档,提升 API 可发现性。
  6. 错误处理build() 方法的错误信息更精准,明确指出缺失的字段名。

Rust 过程宏最佳实践

  1. 优雅处理错误
    • 开发阶段可使用 panic!,发布代码应通过 Diagnostic 报告编译错误(避免运行时崩溃)。
  2. 谨慎使用依赖
    • 仅引入必要的 synquote 功能,通过特性标记(feature flags)减少依赖。
  3. 属性扩展
    • 允许用户通过属性自定义生成逻辑(如 optionaldefault),并清晰记录用法。
  4. 保持可见性
    • 保留原始结构体字段的可见性修饰符(pub/private),确保封装性。
  5. 泛型兼容性
    • 正确处理泛型参数和 where 子句,避免类型信息丢失。
  6. 文档生成
    • 为生成的代码添加注释,便于用户通过 cargo doc 查阅 API。
  7. 充分测试
    • 编写单元测试验证正常逻辑,添加 compile-fail 测试确保错误场景覆盖。
  8. 错误定位
    • 使用 Span 信息标记错误位置,帮助用户快速定位代码问题(如字段缺失错误)。

高级技术

  1. 自定义属性解析
    // 示例:解析更复杂的 builder 属性(如默认值)
    struct BuilderAttribute {
        optional: bool,        // 是否为可选字段
        default: Option<Expr>, // 字段默认值
    }
    
    impl Parse for BuilderAttribute {
        fn parse(input: ParseStream) -> Result<Self> {
            // 解析 #[builder(optional, default = "default_value")] 格式的逻辑
        }
    }
    
  2. 模块化设计
    • 将代码生成逻辑拆分为独立函数(如 generate_setter_method),提高可维护性。
  3. 卫生元编程
    • 使用 Span::call_site() 关联错误到用户代码,避免宏生成代码的作用域污染。
  4. 条件编译
    • 通过 #[cfg(feature = "xyz")] 实现功能开关(如是否生成调试日志)。

完整使用示例

#[derive(Builder, Debug)] // 派生 Builder 和 Debug
pub struct Person {
    name: String,           // 必需字段(无属性标记)
    age: u32,               // 必需字段
    #[builder(optional)]    // 可选字段(允许构建时为 None)
    address: Option<String>,
    emails: Vec<String>,    // Vec<String>,支持链式赋值(如 &str 数组)
}

fn main() {
    // 使用构建器创建实例
    let person = Person::builder()
        .name("Alice")         // 传递 &str,自动转换为 String
        .age(30)
        .emails(vec!["alice@example.com", "alice@work.com"]) // 传递 &str 数组,自动转为 Vec<String>
        .build()               // 构建时检查必需字段,无错误则返回 Ok
        .unwrap();
    
    println!("{:?}", person); // 输出:Person { name: "Alice", age: 30, address: None, emails: [...] }
}