如果你在找 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: