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 は、その体験を
最もシンプルな形で与えてくれる言語でした。
リポジトリ
今回使用したコードはこちらです。
Discussion