🦀

Rust で型安全な Builder Pattern を実現!コンパイラはバグを淘汰する

に公開

はじめに

Rustで開発していると「もっと安全にBuilderパターンを使いたい」と思ったことはありませんか?従来のBuilderパターンは必須フィールドの設定忘れがエラーとして発覚してしまい、「せっかくRustを使っているのに...🥺」という気持ちになってしまいます。

そこで今回は、ラクラク! 超安全性! に Builder を実装できる素晴らしいライブラリ typesafe_builder をご紹介します!

誰のための記事か?(for whom)

  • Builderパターンをもっと安全に使いたい人
  • コンパイル時に制約を検証したい人
  • Rustの型システムの力を活用したい人
  • ランタイムエラーを根絶したい人

なぜ学ぶのか? (why)

  • バグの早期発見: ランタイムエラーをコンパイルエラーに変換できるため
  • コードの信頼性向上: 型レベルで制約を表現できるため
  • 開発体験の向上: IDEのサポートを受けながら安全にコードが書けるため
  • パフォーマンス: ゼロランタイムコストで制約チェックができるため

1. 従来のBuilderパターンの問題点

まず、従来のBuilderパターンでよくある問題を見てみましょう。

// ❌ 従来のBuilderパターン
struct UserBuilder {
    name: Option<String>,
    email: Option<String>,
}

impl UserBuilder {
    fn new() -> Self {
        Self { name: None, email: None }
    }
    
    fn name(mut self, name: String) -> Self {
        self.name = Some(name);
        self
    }
    
    fn email(mut self, email: String) -> Self {
        self.email = Some(email);
        self
    }
    
    fn build(self) -> Result<User, String> {
        let name = self.name.ok_or("name is required")?;
        let email = self.email.ok_or("email is required")?;
        Ok(User { name, email })
    }
}

// このコードはコンパイルが通るが、ランタイムでエラーになる
let user = UserBuilder::new()
    .name("Alice".to_string())
    // .email() を忘れた!
    .build()?; // Err が返ってくる🥺

このようにコンパイル時には検出されず、実行してみないとエラーが分からないという問題があります。

2. TypeSafe Builderの解決策

typesafe_builder では、型システムを活用して必須フィールドの設定忘れをコンパイル時に検出できます!

use typesafe_builder::*;

#[derive(Builder)]
struct User {
    #[builder(required)]
    name: String,
    #[builder(required)]  
    email: String,
}

// ✅ 正しい例
let user = UserBuilder::new()
    .with_name("Alice".to_string())
    .with_email("alice@example.com".to_string())
    .build(); // 成功!

// ❌ emailを忘れた例
let user = UserBuilder::new()
    .with_name("Alice".to_string())
    // .with_email() を忘れた
    .build(); // コンパイルエラー! 🎉

なんと、emailの設定を忘れるとコンパイル時にエラーになります!これで設定忘れによるバグを完全に防げます。

3. 基本的な使い方

3.1 依存関係の追加

まず、Cargo.toml にライブラリを追加します。

[dependencies]
typesafe_builder = "1.0.0"

3.2 基本的なBuilder

use typesafe_builder::*;

#[derive(Builder, Debug)]
struct Config {
    #[builder(required)]
    app_name: String,
    #[builder(optional)]
    debug_mode: Option<bool>,
    #[builder(optional)]
    port: Option<u16>,
}

// 使用例
let config = ConfigBuilder::new()
    .with_app_name("MyApp".to_string())  // 必須
    .with_debug_mode(true)               // オプション
    .with_port(8080)                     // オプション
    .build();

println!("{:?}", config);

ポイントは以下の通りです:

  • #[builder(required)]: 必須フィールド - 設定しないとコンパイルエラー
  • #[builder(optional)]: オプショナルフィールド - 設定しなくても OK

4. 条件付き制約 - 真の威力を発揮!

typesafe_builder の真骨頂は条件付き制約です。「Aを設定したらBも必須」といった複雑な制約を型レベルで表現できます!

4.1 required_if - 条件付き必須フィールド

use typesafe_builder::*;

#[derive(Builder)]
struct Account {
    #[builder(optional)]
    email: Option<String>,
    // emailが設定されたらemail_verifiedも必須!
    #[builder(required_if = "email")]
    email_verified: Option<bool>,
}

// ✅ emailなしの場合
let account1 = AccountBuilder::new().build();

// ✅ email + email_verified の場合  
let account2 = AccountBuilder::new()
    .with_email("user@example.com".to_string())
    .with_email_verified(true)
    .build();

// ❌ emailだけでemail_verifiedなしの場合
let account3 = AccountBuilder::new()
    .with_email("user@example.com".to_string())
    // .with_email_verified() を忘れた
    .build(); // コンパイルエラー!

4.2 optional_if - 条件付きオプショナルフィールド

use typesafe_builder::*;

#[derive(Builder)]
struct ServerConfig {
    #[builder(optional)]
    debug_mode: Option<bool>,
    // debug_modeが設定されていない時は必須!
    #[builder(optional_if = "debug_mode")]
    log_level: Option<String>,
}

// ✅ debug_modeなしなのでlog_levelが必須
let config1 = ServerConfigBuilder::new()
    .with_log_level("INFO".to_string())
    .build();

// ✅ debug_modeありなのでlog_levelはオプション
let config2 = ServerConfigBuilder::new()
    .with_debug_mode(true)
    .build();

4.3 複雑な論理演算子もサポート

なんと、AND (&&)OR (||)NOT (!) も使えます!

use typesafe_builder::*;

#[derive(Builder)]
struct ApiClient {
    #[builder(optional)]
    use_auth: Option<bool>,
    #[builder(optional)]
    use_https: Option<bool>,
    
    // 認証またはHTTPSを使う場合は秘密鍵が必須
    #[builder(required_if = "use_auth || use_https")]
    secret: Option<String>,
    
    // 認証かつHTTPSの場合は証明書が必須
    #[builder(required_if = "use_auth && use_https")]
    certificate: Option<String>,
    
    // 認証もHTTPSも使わない場合は警告メッセージが必須
    #[builder(required_if = "!use_auth && !use_https")]
    insecure_warning: Option<String>,
}

// ✅ 認証とHTTPS両方使用
let client1 = ApiClientBuilder::new()
    .with_use_auth(true)
    .with_use_https(true)
    .with_secret("secret".to_string())
    .with_certificate("cert.pem".to_string())
    .build();

// ✅ セキュアでない設定(警告あり)
let client2 = ApiClientBuilder::new()
    .with_use_auth(false)
    .with_use_https(false)
    .with_insecure_warning("WARNING: Insecure!".to_string())
    .build();

わーいわーい!これだけ複雑な制約が型レベルで表現されて、コンパイル時にチェックされるんです🥺

5. なぜ typesafe_builder が素晴らしいのか

  • ゼロランタイムコスト
    • すべての制約チェックがコンパイル時に行われるため、ランタイムのオーバーヘッドがゼロです。
  • 優れた開発体験
    • IDEが型情報を理解してくれるので、自動補完エラー表示が適切に動作します。

6. 実際の使用例

実際のWebアプリケーションで使いそうな例を見てみましょう。

use typesafe_builder::*;

#[derive(Builder)]
struct DatabaseConfig {
    #[builder(required)]
    host: String,
    #[builder(required)]
    database: String,
    #[builder(optional)]
    use_ssl: Option<bool>,
    #[builder(optional)]
    username: Option<String>,
    
    // ユーザー名が設定されたらパスワードも必須
    #[builder(required_if = "username")]
    password: Option<String>,
    
    // SSLを使う場合は証明書パスが必須
    #[builder(required_if = "use_ssl")]
    ssl_cert_path: Option<String>,
}

// 本番環境用設定
let prod_config = DatabaseConfigBuilder::new()
    .with_host("prod-db.example.com".to_string())
    .with_database("myapp".to_string())
    .with_use_ssl(true)
    .with_ssl_cert_path("/etc/ssl/cert.pem".to_string())
    .with_username("admin".to_string())
    .with_password("super_secret".to_string())
    .build();

// 開発環境用設定(SSL不要)
let dev_config = DatabaseConfigBuilder::new()
    .with_host("localhost".to_string())
    .with_database("myapp_dev".to_string())
    .build();

これなら設定ミスによる本番障害を防げそうですね!

7. まとめ

typesafe_builder は、Rustの型システムの力を最大限活用して、従来のBuilderパターンの問題を根本から解決してくれる素晴らしいライブラリです。

✨ 主な特徴

  • コンパイル時安全性: 設定忘れをコンパイル時に検出
  • ゼロランタイムコスト: すべての検証がコンパイル時に完了
  • 表現力: 複雑な条件付き制約も型レベルで表現可能
  • 使いやすさ: derive マクロで簡単に導入可能

🚀 おすすめポイント

特に以下のような場面での使用をおすすめします:

  • 設定クラス: 複雑な設定の依存関係を型安全に管理
  • APIクライアント: 認証情報などの条件付き必須フィールド
  • ビルダーパターン: より安全で表現力豊かなビルダー

ぜひ皆さんのプロジェクトでも typesafe_builder を使って、より安全で美しいRustコードを書いてみてください!🦀


よろしければ GitHub のフォローもお願いします🥺
https://github.com/tomoikey

参考: https://github.com/tomoikey/typesafe_builder

Discussion