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も必須」といった複雑な制約を型レベルで表現できます!
required_if
- 条件付き必須フィールド
4.1 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(); // コンパイルエラー!
optional_if
- 条件付きオプショナルフィールド
4.2 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();
わーいわーい!これだけ複雑な制約が型レベルで表現されて、コンパイル時にチェックされるんです🥺
typesafe_builder
が素晴らしいのか
5. なぜ - ゼロランタイムコスト
- すべての制約チェックがコンパイル時に行われるため、ランタイムのオーバーヘッドがゼロです。
- 優れた開発体験
- 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
Discussion