Rustで型状態パターンを実装する

この記事では、Rustで型状態パターンを実装する方法を解説します。型状態パターンは、状態によってオブジェクトの振る舞いを変更するデザインパターンです。コード例を交えて説明していきます。

まず、以下の3つの状態を表す構造体を定義します。

```rust
#[derive(Debug, Clone, PartialEq)]
pub struct Unauthenticated;
#[derive(Debug, Clone, PartialEq)]
pub struct Authenticated;
#[derive(Debug, Clone, PartialEq)]
pub struct Resolved;
```



次に、状態に応じて振る舞いが変わる`User`構造体を定義します。`State`というジェネリックパラメータを持ち、状態ごとに変わるデータは`PhantomData`を使って表現しています。

```rust
#[derive(Debug, Clone, PartialEq)]
pub struct User<State = Unauthenticated> {
    id: Option<i32>,
    keycloak_user_id: Option<String>,
    state: PhantomData<State>,
}
```

`PhantomData`は、Rustの標準ライブラリで提供されるゼロサイズの型で、コンパイラに対してジェネリックな型パラメータが構造体にどのように関連付けられているかを示すために使用されます。これにより、コンパイラは、オブジェクトの所有権やライフタイムなど、型システムに関連する重要な情報を正しく扱うことができます。

先ほどの`User`構造体の定義で、`PhantomData`を使っています。

```rust
#[derive(Debug, Clone, PartialEq)]
pub struct User<State = Unauthenticated> {
    id: Option<i32>,
    keycloak_user_id: Option<String>,
    state: PhantomData<State>,
}
```



`User`構造体は、ジェネリックパラメータ`State`を持っていますが、`State`は`User`構造体の中で実際に使用されていません。そのため、`PhantomData<State>`をフィールドに追加することで、`State`型パラメータが構造体に関連付けられていることを示します。これにより、コンパイラは、`State`の所有権やライフタイムを適切に扱うことができ、コンパイル時の型チェックが正しく機能します。

この例では、`PhantomData`を使用することで、状態ごとに異なる振る舞いを実装した型状態パターンを表現しています。`PhantomData`は、Rustでコンパイル時に型システムの制約を満たすための強力なツールです。




`AuthenticatedUser`トレイトを定義し、`Resolved`と`Authenticated`状態に対して実装します。

```rust
pub trait AuthenticatedUser {}

impl AuthenticatedUser for Resolved {}
impl AuthenticatedUser for Authenticated {}
```



`User<T>`に対して、`AuthenticatedUser`トレイトを実装している場合、`keycloak_user_id`メソッドを提供します。また、`is_authenticated`メソッドは常に`true`を返します。

```rust
impl<T> User<T>
where
    T: AuthenticatedUser,
{
    pub fn keycloak_user_id(&self) -> String {
        self.keycloak_user_id.clone().unwrap()
    }

    pub fn is_authenticated() -> bool {
        true
    }
}
```



`User<Unauthenticated>`に対して、`new`メソッド、`is_authenticated`メソッド、そして`authenticate`メソッドを実装します。

```rust
impl User<Unauthenticated> {
    pub fn new() -> Self {
        Self {
            id: None,
            keycloak_user_id: None,
            state: PhantomData,
        }
    }

    pub fn is_authenticated(&self) -> bool {
        false
    }

    pub fn authenticate(&self, keycloak_user_id: String) -> User<Authenticated> {
        User {
            id: self.id,
            keycloak_user_id: Some(keycloak_user_id),
            state: PhantomData,
        }
    }
}
```



`User<Authenticated>`に対しては、`resolve`メソッドを実装します。

```rust
impl User<Authenticated> {
    pub fn resolve(&self, id: i32) -> User<Resolved> {
        User {
            id: Some(id),
            keycloak_user_id: self.keycloak_user_id.clone(),
            state: PhantomData,
        }
    }
}
```



最後に、`User<Resolved>`に対して、`id`メソッドを実装します。

```rust
impl User<Resolved> {
    pub fn id(&self) -> i32 {
        self.id.unwrap()
    }
}
```

これで、型状態パターンを使って、`User`構造体の状態ごとに異なる振る舞いを実装できました。状態遷移図は、Marmeidで以下のように表現できます。

```mermaid
stateDiagram-v2
    Unauthenticated --> Authenticated: authenticate
    Authenticated --> Resolved: resolve
```


この状態遷移図により、`Unauthenticated`状態の`User`は`authenticate`メソッドで`Authenticated`状態に遷移し、さらに`resolve`メソッドで`Resolved`状態に遷移することが分かります。

Rustで型状態パターンを実装することにより、コンパイル時に状態遷移の正しさを保証でき、安全なコードが書けるようになります。また、状態に応じた振る舞いの実装が明確になり、コードの可読性も向上します。



## 実装例全文

```rust
use std::marker::PhantomData;

#[derive(Debug, Clone, PartialEq)]
pub struct Unauthenticated;
#[derive(Debug, Clone, PartialEq)]
pub struct Authenticated;
#[derive(Debug, Clone, PartialEq)]
pub struct Resolved;

#[derive(Debug, Clone, PartialEq)]
pub struct User<State = Unauthenticated> {
    id: Option<i32>,
    keycloak_user_id: Option<String>,
    state: PhantomData<State>,
}

#[derive(thiserror::Error, Debug, PartialEq)]
pub enum GenerateUserError {
    #[error("Invalid ID")]
    InvalidId,

    #[error("Invalid Keycloak User ID")]
    InvalidKeycloakUserId,
}

pub trait AuthenticatedUser {}

impl AuthenticatedUser for Resolved {}
impl AuthenticatedUser for Authenticated {}

impl<T> User<T>
where
    T: AuthenticatedUser,
{
    pub fn keycloak_user_id(&self) -> String {
        self.keycloak_user_id.clone().unwrap()
    }

    pub fn is_authenticated() -> bool {
        true
    }
}

impl User<Unauthenticated> {
    pub fn new() -> Self {
        Self {
            id: None,
            keycloak_user_id: None,
            state: PhantomData,
        }
    }

    pub fn is_authenticated(&self) -> bool {
        false
    }

    pub fn authenticate(&self, keycloak_user_id: String) -> User<Authenticated> {
        User {
            id: self.id,
            keycloak_user_id: Some(keycloak_user_id),
            state: PhantomData,
        }
    }
}

impl User<Authenticated> {
    pub fn resolve(&self, id: i32) -> User<Resolved> {
        User {
            id: Some(id),
            keycloak_user_id: self.keycloak_user_id.clone(),
            state: PhantomData,
        }
    }
}

impl User<Resolved> {
    pub fn id(&self) -> i32 {
        self.id.unwrap()
    }
}
```