型状態Builder
型システムに有効性を組み込みます。
「型状態Builder」はCoddyKit上の無料Learn Rust Codingレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはLearn Rust Coding学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Learn Rust Codingコースには全4レッスンが含まれています。
通常のビルダーが抱える問題
通常のビルダーでは、必須フィールドを設定する前でも、いつでもbuild()を呼び出せます。その場合、データの不足は実行時のpanicやErrとして表面化します。
型状態ビルダーでは、このチェックをコンパイル時に行います。必須の手順を忘れると、コンパイルに失敗します。
型に状態をエンコードする
ポイントは、完了した手順を表すマーカー型を使って、ビルダーをジェネリックにすることです。各フィールドを設定すると、ビルダーの型が変化します。
すべての必須マーカーが"set"状態になったときだけ、build()メソッドが利用可能になります。
struct Missing;
struct Set;マーカーに対してジェネリックなビルダー
必須フィールドごとの状態を表す型パラメーターを、ビルダーに持たせます。PhantomDataは、実際のデータを格納せずにマーカーを保持します。
use std::marker::PhantomData;
struct ReqBuilder<H, U> {
url: Option<String>,
method: Option<String>,
_state: PhantomData<(H, U)>,
}開始状態
コンストラクターは、すべての必須マーカーがMissingになっているビルダーを返します。この時点ではbuild()が存在しないため、型システムはオブジェクトが未完成であることを認識できます。
impl ReqBuilder<Missing, Missing> {
fn new() -> Self {
ReqBuilder { url: None, method: None, _state: PhantomData }
}
}マーカーを遷移させる
セッターは古いビルダーを消費し、そのフィールドのマーカーをSetに変更した新しいビルダーを返します。もう一方のマーカーは、その型パラメーターをジェネリックのままにすることで保持されます。
impl<U> ReqBuilder<Missing, U> {
fn url(self, url: &str) -> ReqBuilder<Set, U> {
ReqBuilder { url: Some(url.to_string()),
method: self.method, _state: PhantomData }
}
}2つ目の遷移
メソッドを設定する場合も同じです。2つ目のマーカーをMissingからSetに変更し、1つ目のマーカーはそのままにします。
impl<H> ReqBuilder<H, Missing> {
fn method(self, m: &str) -> ReqBuilder<H, Set> {
ReqBuilder { url: self.url,
method: Some(m.to_string()), _state: PhantomData }
}
}完全に設定されたときだけbuild()を使える
重要なのは、build()がReqBuilder<Set, Set>に対してのみ実装されていることです。それ以外の状態にはこのメソッドが存在しないため、呼び出すとコンパイルに失敗します。
内部のunwrap呼び出しがpanicすることはありません。型によって、両方のフィールドが存在することが保証されているためです。
struct Request { url: String, method: String }
impl ReqBuilder<Set, Set> {
fn build(self) -> Request {
Request { url: self.url.unwrap(), method: self.method.unwrap() }
}
}全体を組み合わせる
正しいチェーンは、各呼び出しによってビルダーがbuild()の存在するReqBuilder<Set, Set>へ近づくため、問題なくコンパイルできます。
fn demo() -> Request {
ReqBuilder::new()
.url("https://example.com")
.method("GET")
.build()
}望ましいコンパイルエラー
必須の手順を省略すると、コンパイラーが拒否します。ReqBuilder<Set, Missing>でbuild()を呼び出すと「buildという名前のメソッドがない」と報告され、プログラムを実行する前に手順の抜けを検出できます。
// ReqBuilder::new().url("x").build();
// error: no method named `build` found for
// ReqBuilder<Set, Missing>単独で実行できるバージョン
この最小限のプログラムでは、必須フィールドを1つだけ使い、遷移全体を最初から最後まで示します。コンパイルに成功し、構築した値を出力します。
use std::marker::PhantomData;
struct Missing; struct Set;
struct B<N> { name: Option<String>, _s: PhantomData<N> }
impl B<Missing> {
fn new() -> Self { B { name: None, _s: PhantomData } }
fn name(self, n: &str) -> B<Set> {
B { name: Some(n.to_string()), _s: PhantomData }
}
}
impl B<Set> {
fn build(self) -> String { self.name.unwrap() }
}
fn main() {
let v = B::new().name("prod").build();
println!("built: {}", v);
}コストとトレードオフ
型状態ビルダーでは、マーカーが消去されるため、実行時オーバーヘッドなしでコンパイル時の保証を得られます。一方、必須フィールドが増えるほど、型の仕組みが複雑になり、組み合わせごとのimplブロックも増えます。
不完全な構築を仕組み上不可能にする必要があるAPIで、このパターンを使用してください。
理解度チェック
型状態ビルダーで完全性を実際に保証しているものを確認しましょう。
まとめ
型状態ビルダーでは、必須の各手順をマーカー型パラメーターとしてエンコードし、PhantomDataで保持します。セッターはビルダーを消費し、1つのマーカーをSetに変更した新しい型を返します。
build()はすべてがSetの状態に対してのみ実装されているため、手順を忘れると実行時のpanicではなくコンパイルエラーになります。しかも実行時コストはかかりません。
よくある質問
「型状態Builder」レッスンは無料ですか?
はい。「型状態Builder」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Learn Rust Codingコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Learn Rust Codingコースには全4レッスンが含まれています。
「型状態Builder」で何を学びますか?
型システムに有効性を組み込みます。 ブラウザで直接実行するハンズオンコードでLearn Rust Codingを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Learn Rust Codingを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのLearn Rust Codingは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。
「型状態Builder」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このLearn Rust Codingレッスンでコードを書いて実行できますか?
はい。すべてのLearn Rust Codingレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- Builderパターン
- Newtypeパターン
- 型状態Builder
- Derefとラッパーの使いやすさ