Rust型付きBuilderパターン実戦:コンパイル時安全オブジェクト構築の5つのコアパターン

编程语言

Rustの世界にはnullもデフォルトコンストラクタもオプションパラメータもありません—複雑なオブジェクトを構築するには、画面いっぱいのFoo { a: 1, b: 2, ... }を書くか、Builderパターンを使うしかありません。しかし従来のBuilderには致命的な問題があります:.name("xxx")の呼び出しを忘れた?コンパイルエラーにならず、実行時にpanic。 2026年、Rustの型システムがより良い答えをくれます—型状態(Type State)でコンパイル時に必須フィールドの欠落を保証し、typed-builder crateでボイラープレートを排除し、ジェネリックBuilderで条件付き必須を実現します。今日は、手動Builderから型状態、typed-builderからプロダクション級コンポジションまで、Rust型付きBuilderの5つのコアパターンを徹底解説します。

コア概念一覧

概念 説明 主要技術
Builderパターン 複雑なオブジェクトの段階的構築 struct FooBuilder + build()
型状態 ジェネリックパラメータでフィールドの設定状態を追跡 Builder<Missing, Missing>
typed-builder 型安全Builderを自動派生するcrate #[derive(TypedBuilder)]
ジェネリックBuilder ジェネリックパラメータで条件付き必須フィールドを実現 Builder<NameSet = Set>
検証Builder build時にビジネスルール検証を実行 fn build() -> Result<Foo, Error>

問題分析:5つのペインポイント

  1. 必須フィールドの欠落:従来のBuilderのbuild()はOptionを返すかpanic—必須フィールドの未設定を実行時まで発見できない
  2. ボイラープレート爆発:10個のフィールドで10個のsetter、10個のOptionフィールド、1つの巨大なbuild()関数
  3. 条件付き必須の表現不可:フィールドAが設定されたらBも必須、Aが未設定ならBはオプション—このロジックを従来のBuilderはコンパイル時に表現できない
  4. 構築順序の制約:一部のフィールドは他より先に設定する必要がある—従来のBuilderは順序を強制できない
  5. 検証ロジックの分散:フィールド検証が各setterに散在し、build時にもう一度検証—見落としやすい

パターン1:手動Builderと型状態パターン

ジェネリックパラメータで各フィールドの状態を追跡し、コンパイル時に必須フィールドの欠落を防止。

use std::marker::PhantomData;

/// フィールド未設定
pub struct Missing;
/// フィールド設定済み
pub struct Set<T>(PhantomData<T>);

#[derive(Debug, Clone)]
pub struct ServerConfig {
    pub host: String,
    pub port: u16,
    pub max_connections: u32,
    pub timeout_secs: u64,
    pub enable_tls: bool,
    pub tls_cert_path: Option<String>,
}

pub struct ServerConfigBuilder<HostSet, PortSet> {
    host: Option<String>,
    port: Option<u16>,
    max_connections: u32,
    timeout_secs: u64,
    enable_tls: bool,
    tls_cert_path: Option<String>,
    _host: PhantomData<HostSet>,
    _port: PhantomData<PortSet>,
}

impl ServerConfigBuilder<Missing, Missing> {
    pub fn new() -> Self {
        Self {
            host: None, port: None, max_connections: 1000,
            timeout_secs: 30, enable_tls: false, tls_cert_path: None,
            _host: PhantomData, _port: PhantomData,
        }
    }
}

impl<PortSet> ServerConfigBuilder<Missing, PortSet> {
    pub fn host(self, host: impl Into<String>) -> ServerConfigBuilder<Set<String>, PortSet> {
        ServerConfigBuilder {
            host: Some(host.into()), port: self.port,
            max_connections: self.max_connections, timeout_secs: self.timeout_secs,
            enable_tls: self.enable_tls, tls_cert_path: self.tls_cert_path,
            _host: PhantomData, _port: self._port,
        }
    }
}

impl<HostSet> ServerConfigBuilder<HostSet, Missing> {
    pub fn port(self, port: u16) -> ServerConfigBuilder<HostSet, Set<u16>> {
        ServerConfigBuilder {
            host: self.host, port: Some(port),
            max_connections: self.max_connections, timeout_secs: self.timeout_secs,
            enable_tls: self.enable_tls, tls_cert_path: self.tls_cert_path,
            _host: self._host, _port: PhantomData,
        }
    }
}

impl<HostSet, PortSet> ServerConfigBuilder<HostSet, PortSet> {
    pub fn max_connections(mut self, max: u32) -> Self { self.max_connections = max; self }
    pub fn timeout_secs(mut self, secs: u64) -> Self { self.timeout_secs = secs; self }
    pub fn enable_tls(mut self, enable: bool) -> Self { self.enable_tls = enable; self }
    pub fn tls_cert_path(mut self, path: impl Into<String>) -> Self { self.tls_cert_path = Some(path.into()); self }
}

impl ServerConfigBuilder<Set<String>, Set<u16>> {
    pub fn build(self) -> ServerConfig {
        ServerConfig {
            host: self.host.unwrap(), port: self.port.unwrap(),
            max_connections: self.max_connections, timeout_secs: self.timeout_secs,
            enable_tls: self.enable_tls, tls_cert_path: self.tls_cert_path,
        }
    }
}

fn main() {
    // ✅ コンパイル成功:全必須フィールドが設定済み
    let config = ServerConfigBuilder::new()
        .host("0.0.0.0")
        .port(8080)
        .max_connections(5000)
        .timeout_secs(60)
        .enable_tls(true)
        .tls_cert_path("/etc/ssl/cert.pem")
        .build();

    println!("サーバー設定: {:?}", config);

    // ❌ コンパイルエラー:portフィールドが欠落
    // ServerConfigBuilder::new()
    //     .host("0.0.0.0")
    //     .build();
}

重要ポイント

  • MissingSet<T>はゼロサイズ型(ZST)、実行時オーバーヘッドなし
  • build()Set<String>, Set<u16>状態でのみ利用可能、コンパイル時に必須フィールドを保証
  • オプションフィールドはどの状態でも設定可能、型状態を変更しない
  • フィールド設定順序は問わない—host→portでもport→hostでもOK

パターン2:typed-builder crate自動派生

型状態Builderを手動で書くのは煩雑?typed-builder crateが1行のマクロで解決。

use typed_builder::TypedBuilder;
use std::time::Duration;

#[derive(Debug, Clone, TypedBuilder)]
pub struct DatabaseConfig {
    #[builder(default_code = r#""localhost".into()"#)]
    pub host: String,
    #[builder(default = 5432)]
    pub port: u16,
    pub database: String,
    pub username: String,
    pub password: String,
    #[builder(default = 100)]
    pub max_connections: u32,
    #[builder(default = 30)]
    pub timeout_secs: u64,
    #[builder(default = false)]
    pub enable_ssl: bool,
}

#[derive(Debug, Clone, TypedBuilder)]
pub struct HttpClientConfig {
    pub base_url: String,
    #[builder(default = Duration::from_secs(30), setter(into))]
    pub timeout: Duration,
    #[builder(default = 3)]
    pub max_retries: u32,
    #[builder(default, setter(transform = |pairs: Vec<(&str, &str)>| {
        let mut map = reqwest::header::HeaderMap::new();
        for (k, v) in pairs {
            if let (Ok(name), Ok(val)) = (
                reqwest::header::HeaderName::from_bytes(k.as_bytes()),
                reqwest::header::HeaderValue::from_str(v),
            ) {
                map.insert(name, val);
            }
        }
        map
    }))]
    pub headers: reqwest::header::HeaderMap,
}

fn main() {
    let db_config = DatabaseConfig::builder()
        .database("toolsku_prod")
        .username("admin")
        .password("secret123")
        .max_connections(200)
        .build();

    println!("データベース設定: {:?}", db_config);

    let http_config = HttpClientConfig::builder()
        .base_url("https://api.toolsku.com")
        .timeout(Duration::from_secs(60))
        .headers(vec![("Authorization", "Bearer token123")])
        .build();

    println!("HTTPクライアント設定: {:?}", http_config);
}

重要ポイント

  • #[builder(default = ...)]でオプションフィールドをマーク、必須フィールドにはdefaultなし
  • #[builder(setter(into))]でsetterがimpl Into<T>を受け付け、より柔軟に
  • #[builder(setter(transform = ...))]でsetterロジックをカスタマイズ
  • #[builder(default_code = ...)]でコード式をデフォルト値として使用

パターン3:ジェネリックBuilderと条件付き必須フィールド

一部のフィールドは「条件付き必須」—Aが設定されたらBも必須、そうでなければBはオプション。ジェネリックパラメータで正確に表現。

use std::marker::PhantomData;

pub struct TlsEnabled;
pub struct TlsDisabled;

#[derive(Debug)]
pub struct SecureServerConfig {
    pub host: String,
    pub port: u16,
    pub enable_tls: bool,
    pub tls_cert: Option<String>,
    pub tls_key: Option<String>,
}

pub struct SecureServerBuilder<TlsState> {
    host: Option<String>,
    port: Option<u16>,
    enable_tls: bool,
    tls_cert: Option<String>,
    tls_key: Option<String>,
    _tls: PhantomData<TlsState>,
}

impl SecureServerBuilder<TlsDisabled> {
    pub fn new() -> Self {
        Self {
            host: None, port: None, enable_tls: false,
            tls_cert: None, tls_key: None, _tls: PhantomData,
        }
    }

    pub fn enable_tls(self) -> SecureServerBuilder<TlsEnabled> {
        SecureServerBuilder {
            host: self.host, port: self.port, enable_tls: true,
            tls_cert: self.tls_cert, tls_key: self.tls_key, _tls: PhantomData,
        }
    }
}

impl SecureServerBuilder<TlsEnabled> {
    pub fn tls_cert(mut self, cert: impl Into<String>) -> Self { self.tls_cert = Some(cert.into()); self }
    pub fn tls_key(mut self, key: impl Into<String>) -> Self { self.tls_key = Some(key.into()); self }
}

impl<TlsState> SecureServerBuilder<TlsState> {
    pub fn host(mut self, host: impl Into<String>) -> Self { self.host = Some(host.into()); self }
    pub fn port(mut self, port: u16) -> Self { self.port = Some(port); self }
}

impl SecureServerBuilder<TlsDisabled> {
    pub fn build(self) -> Result<SecureServerConfig, String> {
        let host = self.host.ok_or("host is required")?;
        let port = self.port.ok_or("port is required")?;
        Ok(SecureServerConfig { host, port, enable_tls: false, tls_cert: None, tls_key: None })
    }
}

impl SecureServerBuilder<TlsEnabled> {
    pub fn build(self) -> Result<SecureServerConfig, String> {
        let host = self.host.ok_or("host is required")?;
        let port = self.port.ok_or("port is required")?;
        let tls_cert = self.tls_cert.ok_or("tls_cert is required when TLS is enabled")?;
        let tls_key = self.tls_key.ok_or("tls_key is required when TLS is enabled")?;
        Ok(SecureServerConfig { host, port, enable_tls: true, tls_cert: Some(tls_cert), tls_key: Some(tls_key) })
    }
}

fn main() -> Result<(), String> {
    let config_no_tls = SecureServerBuilder::new()
        .host("0.0.0.0")
        .port(8080)
        .build()?;

    let config_with_tls = SecureServerBuilder::new()
        .host("0.0.0.0")
        .port(443)
        .enable_tls()
        .tls_cert("/etc/ssl/cert.pem")
        .tls_key("/etc/ssl/key.pem")
        .build()?;

    Ok(())
}

重要ポイント

  • TlsEnabled/TlsDisabledでTLS状態をマーク、異なる状態で異なるbuild()実装
  • TLS有効化で状態遷移、コンパイラがTLS有効化を「認識」
  • 条件付き必須フィールドの検証はbuild()で完了、エラーメッセージがより明確

パターン4:検証Builderとbuild時チェック

ビジネスルール検証はsetterに散在させるべきではなく、build()時に統一実行すべき。

use std::collections::HashMap;

#[derive(Debug)]
pub enum ValidationError {
    FieldRequired { field: String, reason: String },
    FieldRange { field: String, min: String, max: String, actual: String },
    Custom(String),
}

impl std::fmt::Display for ValidationError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::FieldRequired { field, reason } => write!(f, "フィールド'{}'は必須: {}", field, reason),
            Self::FieldRange { field, min, max, actual } => {
                write!(f, "フィールド'{}'の範囲エラー: 期待{}~{}, 実際{}", field, min, max, actual)
            }
            Self::Custom(msg) => write!(f, "{}", msg),
        }
    }
}

#[derive(Debug, Clone, PartialEq)]
pub enum EvictionPolicy { Lru, Lfu, Fifo, Ttl }

#[derive(Debug, Clone)]
pub struct CacheConfig {
    pub max_size_mb: u64,
    pub ttl_secs: u64,
    pub eviction_policy: EvictionPolicy,
    pub namespace: String,
    pub enable_compression: bool,
    pub compression_level: u32,
    pub tags: HashMap<String, String>,
}

pub struct CacheConfigBuilder {
    max_size_mb: Option<u64>,
    ttl_secs: Option<u64>,
    eviction_policy: Option<EvictionPolicy>,
    namespace: Option<String>,
    enable_compression: Option<bool>,
    compression_level: Option<u32>,
    tags: HashMap<String, String>,
}

impl CacheConfigBuilder {
    pub fn new() -> Self {
        Self {
            max_size_mb: None, ttl_secs: None, eviction_policy: None,
            namespace: None, enable_compression: None, compression_level: None,
            tags: HashMap::new(),
        }
    }

    pub fn max_size_mb(mut self, size: u64) -> Self { self.max_size_mb = Some(size); self }
    pub fn ttl_secs(mut self, secs: u64) -> Self { self.ttl_secs = Some(secs); self }
    pub fn eviction_policy(mut self, policy: EvictionPolicy) -> Self { self.eviction_policy = Some(policy); self }
    pub fn namespace(mut self, ns: impl Into<String>) -> Self { self.namespace = Some(ns.into()); self }
    pub fn enable_compression(mut self, enable: bool) -> Self { self.enable_compression = Some(enable); self }
    pub fn compression_level(mut self, level: u32) -> Self { self.compression_level = Some(level); self }
    pub fn tag(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
        self.tags.insert(key.into(), value.into()); self
    }

    pub fn build(self) -> Result<CacheConfig, Vec<ValidationError>> {
        let mut errors = Vec::new();

        let max_size_mb = match self.max_size_mb {
            Some(v) if v > 0 => v,
            Some(v) => { errors.push(ValidationError::FieldRange { field: "max_size_mb".into(), min: "1".into(), max: "unlimited".into(), actual: v.to_string() }); v }
            None => { errors.push(ValidationError::FieldRequired { field: "max_size_mb".into(), reason: "キャッシュには最大容量が必要".into() }); 0 }
        };

        let ttl_secs = match self.ttl_secs {
            Some(v) if v > 0 => v,
            Some(v) => { errors.push(ValidationError::FieldRange { field: "ttl_secs".into(), min: "1".into(), max: "unlimited".into(), actual: v.to_string() }); v }
            None => { errors.push(ValidationError::FieldRequired { field: "ttl_secs".into(), reason: "キャッシュにはTTLが必要".into() }); 0 }
        };

        let enable_compression = self.enable_compression.unwrap_or(false);
        if enable_compression && self.compression_level.is_none() {
            errors.push(ValidationError::FieldRequired { field: "compression_level".into(), reason: "圧縮有効時は圧縮レベルの設定が必要".into() });
        }

        if !errors.is_empty() { return Err(errors); }

        Ok(CacheConfig {
            max_size_mb, ttl_secs,
            eviction_policy: self.eviction_policy.unwrap_or(EvictionPolicy::Lru),
            namespace: self.namespace.unwrap_or_else(|| "default".into()),
            enable_compression,
            compression_level: self.compression_level.unwrap_or(6),
            tags: self.tags,
        })
    }
}

fn main() -> Result<(), Vec<ValidationError>> {
    let config = CacheConfigBuilder::new()
        .max_size_mb(1024)
        .ttl_secs(300)
        .eviction_policy(EvictionPolicy::Lru)
        .namespace("toolsku_cache")
        .enable_compression(true)
        .compression_level(6)
        .tag("env", "production")
        .build()?;

    println!("キャッシュ設定: {:?}", config);
    Ok(())
}

重要ポイント

  • 全検証エラーを収集して一度に返す、最初のエラーで停止しない
  • 検証ロジックはbuild()に集中、setterは純粋に保つ
  • 条件付き検証(「圧縮有効時は圧縮レベル必須」など)はbuildで統一処理
  • Result<T, Vec<ValidationError>>を返す、UI層での全エラー表示に便利

パターン5:プロダクション級Builderコンポジションパターン

実際のプロジェクトでは、複数のBuilderを組み合わせる必要があります。コンポジションパターンで複雑な設定を構築。

use typed_builder::TypedBuilder;
use std::collections::HashMap;

#[derive(Debug, Clone, TypedBuilder)]
pub struct NetworkConfig {
    #[builder(default_code = r#""0.0.0.0".into()"#)]
    pub bind_address: String,
    #[builder(default = 8080)]
    pub port: u16,
    #[builder(default = 100)]
    pub max_connections: u32,
    #[builder(default = 30)]
    pub timeout_secs: u64,
    #[builder(default = true)]
    pub enable_keepalive: bool,
}

#[derive(Debug, Clone, TypedBuilder)]
pub struct StorageConfig {
    pub backend: StorageBackend,
    #[builder(default = 1024)]
    pub max_size_mb: u64,
    #[builder(default = 300)]
    pub ttl_secs: u64,
    #[builder(default = 3)]
    pub replication_factor: u32,
}

#[derive(Debug, Clone, PartialEq)]
pub enum StorageBackend { Redis, Memcached, InMemory }

#[derive(Debug, Clone, TypedBuilder)]
pub struct SecurityConfig {
    #[builder(default = true)]
    pub enable_auth: bool,
    #[builder(default_code = r#"Some("HS256".into())"#)]
    pub jwt_algorithm: Option<String>,
    #[builder(default)]
    pub allowed_origins: Vec<String>,
    #[builder(default = 3600)]
    pub token_expiry_secs: u64,
}

#[derive(Debug, Clone, TypedBuilder)]
pub struct LoggingConfig {
    #[builder(default_code = r#""info".into()"#)]
    pub level: String,
    #[builder(default = true)]
    pub enable_json_format: bool,
    #[builder(default_code = r#"Some("stdout".into())"#)]
    pub output_path: Option<String>,
    #[builder(default)]
    pub extra_fields: HashMap<String, String>,
}

#[derive(Debug, Clone, TypedBuilder)]
pub struct AppConfig {
    pub app_name: String,
    #[builder(default_code = r#""1.0.0".into()"#)]
    pub version: String,
    pub network: NetworkConfig,
    pub storage: StorageConfig,
    pub security: SecurityConfig,
    pub logging: LoggingConfig,
}

impl AppConfig {
    pub fn development(app_name: impl Into<String>) -> AppConfigBuilder {
        AppConfig::builder()
            .app_name(app_name)
            .network(NetworkConfig::builder().bind_address("127.0.0.1").port(3000).max_connections(10).build())
            .storage(StorageConfig::builder().backend(StorageBackend::InMemory).max_size_mb(128).build())
            .security(SecurityConfig::builder().enable_auth(false).build())
            .logging(LoggingConfig::builder().level("debug").enable_json_format(false).build())
    }

    pub fn production(app_name: impl Into<String>) -> AppConfigBuilder {
        AppConfig::builder()
            .app_name(app_name)
            .network(NetworkConfig::builder().bind_address("0.0.0.0").port(8080).max_connections(10000).timeout_secs(60).build())
            .storage(StorageConfig::builder().backend(StorageBackend::Redis).max_size_mb(4096).replication_factor(3).build())
            .security(SecurityConfig::builder().enable_auth(true).jwt_algorithm("RS256").allowed_origins(vec!["https://toolsku.com".into()]).token_expiry_secs(7200).build())
            .logging(LoggingConfig::builder().level("info").enable_json_format(true).output_path("/var/log/toolsku/app.log").build())
    }
}

fn main() {
    let dev_config = AppConfig::development("toolsku-dev").version("0.1.0").build();
    println!("開発設定: {:?}", dev_config);

    let prod_config = AppConfig::production("toolsku").version("2.5.0").build();
    println!("本番設定: {:?}", prod_config);
}

重要ポイント

  • 各サブ設定に独立したBuilder、責務が明確
  • AppConfigはサブBuilderのコンポジションで構築、階層が明確
  • プリセットメソッド(development()/production())がすぐ使える設定を提供
  • プリセットはAppConfigBuilderを返す、さらにカスタマイズしてプリセット値を上書き可能

よくある落とし穴

落とし穴1:型状態BuilderでPhantomData忘れ

// ❌ 間違い:ジェネリックパラメータが未使用
struct Builder<HostSet> {
    host: Option<String>,
    // error[E0392]: parameter `HostSet` is never used
}

// ✅ 正しい:PhantomDataでジェネリックパラメータをマーク
struct Builder<HostSet> {
    host: Option<String>,
    _host: PhantomData<HostSet>,
}

落とし穴2:typed-builderのデフォルト値型不一致

// ❌ 間違い:default値の型がフィールド型と不一致
#[derive(TypedBuilder)]
struct Config {
    #[builder(default = "localhost")]  // &strをStringに代入不可
    pub host: String,
}

// ✅ 正しい:default_codeを使用
#[derive(TypedBuilder)]
struct Config {
    #[builder(default_code = r#""localhost".into()"#)]
    pub host: String,
}

落とし穴3:BuilderがSend/Syncでない

// ❌ 間違い:PhantomData<*const ()>はSend/Syncではない
struct Builder<HostSet> {
    _host: PhantomData<*const ()>,
}

// ✅ 正しい:PhantomData<fn() -> HostSet>を使用
struct Builder<HostSet> {
    _host: PhantomData<fn() -> HostSet>,  // fn() -> TはSend+Sync
}

落とし穴4:型状態遷移でのフィールド欠落

// ❌ 間違い:遷移時に全フィールドの引き渡し忘れ
impl Builder<Missing> {
    fn host(self, host: String) -> Builder<Set<String>> {
        Builder {
            host: Some(host),
            _host: PhantomData,
            // portフィールドが欠落!
        }
    }
}

// ✅ 正しい:全フィールドが引き渡されることを確認
impl Builder<Missing> {
    fn host(self, host: String) -> Builder<Set<String>> {
        Builder {
            host: Some(host),
            port: self.port,
            _host: PhantomData,
        }
    }
}

落とし穴5:検証Builderが単一Errorを返す

// ❌ 間違い:最初のエラーのみ返す
fn build(self) -> Result<Config, ValidationError> {
    let host = self.host.ok_or(ValidationError::FieldRequired("host"))?;
    let port = self.port.ok_or(ValidationError::FieldRequired("port"))?;
    Ok(Config { host, port })
}

// ✅ 正しい:全エラーを収集して一度に返す
fn build(self) -> Result<Config, Vec<ValidationError>> {
    let mut errors = Vec::new();
    // ... 全エラーを収集 ...
    if !errors.is_empty() { return Err(errors); }
    Ok(Config { /* ... */ })
}

エラートラブルシューティング表

エラー現象 可能な原因 調査方法 解決策
"no method named build" 必須フィールド未設定 Builderのジェネリック状態を確認 全必須フィールドのsetterが呼ばれていることを確認
E0392 parameter never used PhantomData欠落 ジェネリックパラメータが使用されているか確認 PhantomData<T>フィールドを追加
型不一致 default値の型エラー #[builder(default = ...)]を確認 default_codeまたは正しい型を使用
BuilderがSendでない PhantomDataが生ポインタを使用 PhantomDataの型パラメータを確認 PhantomData<fn() -> T>を使用
フィールド値の欠落 型状態遷移でフィールド引き渡し漏れ implブロックのフィールド引き渡しを確認 遷移で全フィールドが代入されることを確認
コンパイル時間が長い ジェネリックパラメータが多すぎる Builderのジェネリック数を確認 typed-builderで手動ジェネリックを削減
setterがチェーン不可 setterがSelfを返すがmutを使用 setterシグネチャを確認 Selfを返すことを確認(&mut Selfではない)
循環依存 AのbuildがBを必要、BのbuildがAを必要 Builder間の依存関係を確認 独立したBuilderに分割
検証ロジックの重複 setterとbuildの両方で検証 検証コードの位置を確認 buildでのみ検証
デフォルト値が反映されない #[builder(default)]の付け忘れ オプションフィールドの注釈を確認 デフォルト値のあるフィールドにdefaultを追加

高度な最適化

  1. マクロで型状態を簡素化declare_builder!マクロを書き、型状態Builderのボイラープレートコードを自動生成

  2. Builderシリアライズ:Builderがserde::Serializeをサポート、半完成の設定をファイルに保存可能に

  3. 環境変数統合:Builderのsetterが環境変数をデフォルト値として自動読み取り、.port_from_env("APP_PORT")など

  4. 設定ホットアップデート:Builderで構築した設定オブジェクトがmerge()メソッドをサポート、実行時に新設定をマージ可能

  5. コンパイル時フィールドカウント:const genericで設定済みフィールド数を追跡、コンパイル時に最低N個のフィールド設定を保証

アプローチ比較

アプローチ コンパイル時安全性 ボイラープレート 柔軟性 適用シナリオ
従来のBuilder ⭐⭐⭐ ⭐⭐⭐⭐⭐ 単純なオブジェクト
型状態Builder ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐ 厳格な必須フィールド
typed-builder ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ほとんどのプロジェクト
検証Builder ⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐⭐ 複雑なビジネスルール
コンポジションBuilder ⭐⭐⭐⭐⭐ ⭐⭐ ⭐⭐⭐⭐ マルチモジュール設定

まとめ

Rustの型システムはBuilderパターンを「実行時の祈り」から「コンパイル時の保証」へと進化させます—型状態がコンパイル時にフィールド状態を追跡し、typed-builderがボイラープレートを排除し、ジェネリックBuilderが条件付き必須を表現し、検証Builderがビジネスルールを統一し、コンポジションBuilderが複雑な設定を構築します。 覚えておいてください:Rust Builderの真髄は「メソッドチェーン」ではなく「不正な状態を表現不可能にすること」です。設定が構築できるなら、それは必ず正当です。

オンラインツールおすすめ

ブラウザローカルツールを無料で試す →

#Rust Builder#类型状态#typed-builder#Rust设计模式#2026#编程语言