型状態プログラミング

型状態の概念は、オブジェクトの現在の状態に関する情報を、そのオブジェクトの型にエンコードすることを説明しています。 これは、少し難解に思えますが、Rustのビルダーパターンを使ったことがあるならば、既に型状態プログラミングを使い始めています。

#[derive(Debug)]
struct Foo {
    inner: u32,
}

struct FooBuilder {
    a: u32,
    b: u32,
}

impl FooBuilder {
    pub fn new(starter: u32) -> Self {
        Self {
            a: starter,
            b: starter,
        }
    }

    pub fn double_a(self) -> Self {
        Self {
            a: self.a * 2,
            b: self.b,
        }
    }

    pub fn into_foo(self) -> Foo {
        Foo {
            inner: self.a + self.b,
        }
    }
}

fn main() {
    let x = FooBuilder::new(10)
        .double_a()
        .into_foo();

    println!("{:#?}", x);
}

この例では、Fooオブジェクトを直接作る方法はありません。必要なFooオブジェクトを取得する前に、FooBuilderを作り、正しく初期化しなければなりません。

この最小限の例は、2つの状態をエンコードしています。

  • FooBuilderは、「未設定」もしくは「設定中」の状態を表現します。
  • Fooは、「設定済み」もしくは「使用準備完了」の状態を意味します。

強い型

Rustは強い型付けシステムを持っています。Fooのインスタンスを魔法のように作成したり、 into_foo()メソッドを呼び出すことなしにFooBuilderからFooに変換したりする、簡単な方法はありません。 さらに、into_foo()メソッドは、オリジナルのFooBuilder構造体を消費します。 これは、新しいインスタンスを作成しないと、再利用ができないことを意味します。

このことにより、システムの状態を型として表現することが可能になります。 そして、状態遷移に必要なアクションを、ある型と別の型とを交換するメソッドに取り入れることができます。 FooBuilderを作成し、Fooオブジェクトに交換することで、基本的なステートマシンのステップを見てきました。