Rust `Builder` 派生宏实现说明
Rust Builder
派生宏实现说明
需求
编写宏为结构体生成构建器模式,具备以下特性:
- 构建器中的可选字段
- 每个字段的 setter 方法(返回
self
支持链式调用) - 对
String
和Vec<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
}
})
}
关键点
- 泛型支持:改进后的宏正确处理原始结构体中的泛型类型和约束。
- 属性自定义:通过
#[builder(...)]
属性定制行为:#[builder(optional)]
标记可选字段(构建时允许为None
)。
- 类型处理增强:
- 更健壮地处理
String
、Vec<String>
和通用Vec<T>
、Option<T>
类型。
- 更健壮地处理
- 便捷方法:
- 原始结构体新增
builder()
方法,直接创建构建器。 - 构建器自动派生
Default
,支持默认初始化。
- 原始结构体新增
- 文档注释:每个生成的方法包含说明文档,提升 API 可发现性。
- 错误处理:
build()
方法的错误信息更精准,明确指出缺失的字段名。
Rust 过程宏最佳实践
- 优雅处理错误:
- 开发阶段可使用
panic!
,发布代码应通过Diagnostic
报告编译错误(避免运行时崩溃)。
- 开发阶段可使用
- 谨慎使用依赖:
- 仅引入必要的
syn
和quote
功能,通过特性标记(feature flags)减少依赖。
- 仅引入必要的
- 属性扩展:
- 允许用户通过属性自定义生成逻辑(如
optional
、default
),并清晰记录用法。
- 允许用户通过属性自定义生成逻辑(如
- 保持可见性:
- 保留原始结构体字段的可见性修饰符(
pub
/private
),确保封装性。
- 保留原始结构体字段的可见性修饰符(
- 泛型兼容性:
- 正确处理泛型参数和
where
子句,避免类型信息丢失。
- 正确处理泛型参数和
- 文档生成:
- 为生成的代码添加注释,便于用户通过
cargo doc
查阅 API。
- 为生成的代码添加注释,便于用户通过
- 充分测试:
- 编写单元测试验证正常逻辑,添加
compile-fail
测试确保错误场景覆盖。
- 编写单元测试验证正常逻辑,添加
- 错误定位:
- 使用
Span
信息标记错误位置,帮助用户快速定位代码问题(如字段缺失错误)。
- 使用
高级技术
- 自定义属性解析:
// 示例:解析更复杂的 builder 属性(如默认值) struct BuilderAttribute { optional: bool, // 是否为可选字段 default: Option<Expr>, // 字段默认值 } impl Parse for BuilderAttribute { fn parse(input: ParseStream) -> Result<Self> { // 解析 #[builder(optional, default = "default_value")] 格式的逻辑 } }
- 模块化设计:
- 将代码生成逻辑拆分为独立函数(如
generate_setter_method
),提高可维护性。
- 将代码生成逻辑拆分为独立函数(如
- 卫生元编程:
- 使用
Span::call_site()
关联错误到用户代码,避免宏生成代码的作用域污染。
- 使用
- 条件编译:
- 通过
#[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: [...] }
}
本文是原创文章,采用 CC BY-NC-ND 4.0 协议,完整转载请注明来自 Unic
评论
匿名评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果