🏹

Rustで体験する「ステートマシンがなぜ重要か」 ── 信号機で理解する最小FSM

に公開

はじめに

本記事では「ステートマシン」と「オートマトン」という言葉をほぼ同義として扱います。
厳密には、

  • オートマトン:理論・数学寄りの呼び方
  • ステートマシン:実装・設計寄りの呼び方

という違いがありますが、
プログラムとしてやっていることは同じです。

本記事では「状態」と「遷移」をコードでどう表現するか、
という実装視点に集中するため、細かな理論的区別には踏み込みません。

「ステートマシン(FSM)」という言葉を聞いたことはあるけれど、

  • 正直よく分からない
  • 理論っぽくて難しそう
  • if 文で十分では?

そう感じている人は多いと思います。
正直、私自身もそうでした。

この記事では、あえて一番シンプルな題材である「信号機」を使い、

  • ステートマシンとは何か
  • なぜわざわざ使うのか
  • if 文と何が違うのか

を Rust の最小コードで体験的に説明します。

ステートマシンとは何か(定義はこれだけ)

ステートマシンとは、

「状態」と「状態遷移」を明示的に定義する考え方
です。

難しい理論は一切不要で、
本質は次の形に集約されます。

現在の状態 + 入力 → 次の状態

なぜ信号機なのか

理由は単純で、

  • 状態が少ない
  • 誰でも仕様を知っている
  • 本質(状態と遷移)だけに集中できる

からです。

ビジネスロジックを使うと、余計な要素が増えます。
「書き方」を学ぶなら、信号機が最適です。


Rustで書く最小ステートマシン

以下が、今回のコアとなるコードです。

#[derive(Debug)]
enum State {
    Red,
    Green,
    Yellow,
}

fn next(state: State) -> State {
    match state {
        State::Red => State::Green,
        State::Green => State::Yellow,
        State::Yellow => State::Red,
    }
}

fn main() {
    let mut state = State::Red;

    for _ in 0..6 {
        println!("current state: {:?}", state);
        state = next(state);
    }
}

やっていることは非常に単純です。

  • 状態を enum で定義
  • 状態遷移を match で表現
  • if 文は一切使わない

このコードの「本当の価値」

一見すると、
「if 文で書けるのでは?」と思うかもしれません。
重要なのは ここから です。

enum State {
    Red,
    Green,
    Yellow,
    Blink,
}

すると、Rust は即座にコンパイルエラーを出します。

non-exhaustive patterns

つまり、

「Blink の扱いが未定義ですよ」

と、コンパイラが教えてくれます。

if 文では何が起きるか

if 文で状態管理をすると、こうなりがちです。

  • 状態が増えても気づかない
  • 条件分岐が散らばる
  • 「ありえない状態」が生まれる

結果として、

  • 実行して初めてバグに気づく
  • テスト漏れで本番障害になる
    という流れになります。

Rustのステートマシンが違う点

Rustでは、

  • 状態は enum で閉じた集合
  • 遷移は match で網羅必須
  • 漏れは コンパイル時にエラー

になります。
これはつまり、

設計ミスが「書けない」

という状態です。

他言語でも同じことはできる?

結論から言うと、できます。
以下は同じ信号機FSMを、他言語で書いた例です。

PHP(8.1+)

enum State {
    case RED;
    case GREEN;
    case YELLOW;
}

function nextState(State $state): State {
    return match ($state) {
        State::RED => State::GREEN,
        State::GREEN => State::YELLOW,
        State::YELLOW => State::RED,
    };
}

Python(3.10+)

from enum import Enum, auto

class State(Enum):
    RED = auto()
    GREEN = auto()
    YELLOW = auto()

def next_state(state):
    match state:
        case State.RED:
            return State.GREEN
        case State.GREEN:
            return State.YELLOW
        case State.YELLOW:
            return State.RED

Ruby

def next_state(state)
  case state
  when :red then :green
  when :green then :yellow
  when :yellow then :red
  end
end

それでもRustで体験する意味

他言語と比べたとき、Rustの決定的な違いはこれです。

  • 状態追加=必ず修正が強制される
  • テスト以前に コンパイルが止まる
  • 仕様漏れが「実行前」に分かる

信号機は単なる例ですが、

  • APIのステータス
  • 業務フロー
  • UIの状態管理

すべて同じ構造を持っています。

まとめ

ステートマシンは、

  • 難しい理論でも
  • 特別なライブラリでもありません。

本質は、

「ありえない状態を、作れなくする」

ことです。

Rust は、その体験を
最もシンプルな形で与えてくれる言語でした。

リポジトリ

今回使用したコードはこちらです。
https://github.com/meta-taro/rust-fsm-traffic-light

Discussion