如果你在找 drive_builder,crates.io 上目前没有这个 Rust crate。更常见、也更符合这个名字含义的 crate 是 derive_builder:它通过 #[derive(Builder)] 为结构体自动生成 Builder 模式代码,减少大量重复 setter、默认值和校验逻辑。
它解决什么问题
Rust 结构体字段一多,直接用字面量初始化很快会变得难读:
struct ServerConfig {
host: String,
port: u16,
tls: bool,
workers: usize,
timeout_ms: u64,
}
let config = ServerConfig {
host: "127.0.0.1".to_string(),
port: 8080,
tls: false,
workers: 4,
timeout_ms: 30_000,
};
如果这个结构体经常被构造,或者有很多可选配置,Builder 模式会更清楚:
let config = ServerConfigBuilder::default()
.host("127.0.0.1".to_string())
.port(8080)
.workers(4)
.build()?;
手写 Builder 不难,但字段一多就很机械。derive_builder 的价值就是把这些机械代码交给宏生成。
基本用法
先添加依赖:
[dependencies]
derive_builder = "0.20.2"
然后在结构体上派生 Builder:
use derive_builder::Builder;
#[derive(Debug, Builder)]
struct ServerConfig {
host: String,
port: u16,
tls: bool,
workers: usize,
timeout_ms: u64,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ServerConfigBuilder::default()
.host("127.0.0.1".to_string())
.port(8080)
.tls(false)
.workers(4)
.timeout_ms(30_000)
.build()?;
println!("{config:?}");
Ok(())
}
默认情况下,derive_builder 会生成一个 ServerConfigBuilder。每个字段在 builder 内部会被包成 Option<T>,调用 build() 时检查必填字段是否都已经设置。如果漏掉字段,build() 会返回错误,而不是偷偷生成一个不完整对象。
常用属性
让 setter 更好用
很多字段是 String,但调用者经常传 &str。可以用 setter(into) 让 setter 接受所有能转换成目标类型的值:
use derive_builder::Builder;
#[derive(Debug, Builder)]
#[builder(setter(into))]
struct ServerConfig {
host: String,
port: u16,
}
let config = ServerConfigBuilder::default()
.host("localhost")
.port(3000_u16)
.build()?;
这类配置建议放在结构体级别。如果只有某个字段需要,也可以写到字段上:
#[derive(Debug, Builder)]
struct User {
#[builder(setter(into))]
name: String,
age: u8,
}
默认值
不是所有字段都应该强制调用者填写。可以用 #[builder(default)] 走字段类型的 Default,也可以写明确表达式:
use derive_builder::Builder;
#[derive(Debug, Builder)]
struct ServerConfig {
#[builder(default = "\"127.0.0.1\".to_string()")]
host: String,
#[builder(default = "8080")]
port: u16,
#[builder(default)]
tls: bool,
}
let config = ServerConfigBuilder::default().build()?;
如果结构体大部分字段都有默认值,可以把 default 放在结构体上,再对少数字段覆盖:
#[derive(Debug, Builder)]
#[builder(default)]
struct ServerConfig {
host: String,
port: u16,
tls: bool,
}
Option 字段
对 Option<T> 字段,默认 setter 需要传 Some(value)。如果希望调用端直接传 value,可以使用 strip_option:
use derive_builder::Builder;
#[derive(Debug, Builder)]
struct Job {
name: String,
#[builder(setter(strip_option), default)]
retry_limit: Option<u32>,
}
let job = JobBuilder::default()
.name("sync-users".to_string())
.retry_limit(3)
.build()?;
这里的 default 很重要:不调用 retry_limit(...) 时,字段会是 None。
Vec 字段逐个追加
集合字段常见需求是重复追加。each 可以生成单项追加方法:
use derive_builder::Builder;
#[derive(Debug, Builder)]
struct Pipeline {
#[builder(setter(each = "step"))]
steps: Vec<String>,
}
let pipeline = PipelineBuilder::default()
.step("fetch".to_string())
.step("transform".to_string())
.step("load".to_string())
.build()?;
这比每次手动构造 Vec 更适合配置式 API。
构建前校验
Builder 不只是为了少写字段名,也适合集中校验构造规则。derive_builder 支持在 build() 前调用校验函数:
use derive_builder::Builder;
#[derive(Debug, Builder)]
#[builder(build_fn(validate = "Self::validate"))]
struct ServerConfig {
port: u16,
#[builder(default = "1")]
workers: usize,
}
impl ServerConfigBuilder {
fn validate(&self) -> Result<(), String> {
if matches!(self.port, Some(0)) {
return Err("port cannot be 0".to_string());
}
if matches!(self.workers, Some(0)) {
return Err("workers cannot be 0".to_string());
}
Ok(())
}
}
需要注意:默认值是在校验之后应用的。所以如果一个字段依赖默认值,也要在校验逻辑里处理 None 的情况,或者把校验放到自定义构造函数里。
Builder 模式选择
derive_builder 默认生成的是可变 builder:setter 接收 &mut self,返回 &mut Self。这是官方文档中推荐的默认模式,链式调用简单,也不会每次 setter 都移动整个 builder。
它也支持其他模式:
#[derive(Builder)]
#[builder(pattern = "owned")]
struct OwnedConfig {
name: String,
}
#[derive(Builder)]
#[builder(pattern = "immutable")]
struct ImmutableConfig {
name: String,
}
一般项目里优先用默认模式就够了。只有在 API 设计明确需要消费式链式调用,或者需要不可变 builder 时,再考虑切换。
适合用在哪里
derive_builder 适合这些场景:
- 配置结构体字段很多,有必填和可选字段混合。
- SDK、客户端、任务参数等 API 希望调用端读起来像声明配置。
- 构造前需要集中校验,而不是把校验散落在多个调用点。
- 结构体还在演进,新字段可能继续增加。
不太适合这些场景:
- 结构体只有两三个字段,字面量初始化已经足够清晰。
- 对编译时间和宏展开极度敏感的底层 crate。
- 需要非常精细控制 builder 内部状态类型,例如类型状态机 builder。
小结
derive_builder 是一个务实的 Rust 过程宏 crate:它不会改变你的数据模型,只是替你生成结构体对应的 Builder API。对业务配置、请求参数、任务描述这类结构体来说,它能明显减少样板代码,也让默认值、可选字段和校验规则更集中。
我通常会在字段数量开始变多、构造逻辑开始出现默认值和校验时引入它;如果只是简单数据结构,就继续用普通结构体初始化。Builder 模式本身不是目标,让调用端更清楚、更少出错才是目标。
Sources: