🎯

Enumとパターンマッチング — Symbolとcase/whenの強力な進化版

Rustのenumはデータを持てる — Rubyにはない概念

Rubyでは状態をSymbolで表す。:active:inactive:pending。Rustのenumがこの役割を担う。

enum Status {
    Active,
    Inactive,
    Pending,
}

let s = Status::Active;

ここまではRubyのSymbolに近い。だがRust enumの本当の力は各バリアントにデータを持てることだ。

データを持つenum

enum Message {
    Quit,                       // データなし
    Text(String),               // String1つ
    Move { x: i32, y: i32 },    // 名前付きフィールド
    Color(u8, u8, u8),          // 複数の値
}

let msg = Message::Text(String::from("hello"));
let pos = Message::Move { x: 10, y: 20 };

Rubyでこれをやるにはクラス継承やHashが必要。Rustではenum1つで「この型はこれらの形のどれか」を表現する。

match — 強制的な網羅的マッチング

Rubyのcase/whenに対応するが、全てのケースを処理しなければならない

# Ruby — elseなしでもOK
case status
when :active then "活性"
when :inactive then "非活性"
# :pendingを漏らしてもエラーなし
end

// Rust — 漏れたらコンパイルエラー
match status {
    Status::Active => "活性",
    Status::Inactive => "非活性",
    Status::Pending => "待機",  // 漏らすとerror[E0004]
}

なぜこれが良いか?後でenumに新しいバリアントを追加すると、matchを使った全箇所でコンパイルエラーが出る。未処理のケースを絶対に見逃さない。

matchでデータ抽出

match msg {
    Message::Quit => println!("終了"),
    Message::Text(text) => println!("テキスト: {}", text),
    Message::Move { x, y } => println!("移動: ({}, {})", x, y),
    Message::Color(r, g, b) => println!("色: {},{},{}", r, g, b),
}

enum内のデータを取り出しながら同時に分岐する。Rubyでcase/whenで型チェックとデータ取得を一度にやるようなもの。

if let — 1つだけチェックしたい時

matchが全ケースを処理するのに対し、1つだけ確認したい時はif letを使う。

if let Message::Text(text) = msg {
    println!("テキスト: {}", text);
}
// 残りは無視

Rubyのif status == :activeに近い感覚だが、データ抽出まで同時にできる。

OptionとResultもenumだ

Option<T>Some(T)またはNoneResult<T, E>Ok(T)またはErr(E)。どちらもenum。Rustのエラー処理はenumの上に構築されている。

キーポイント

1

enumはRubyのSymbolグループ + データを持てる進化版

2

matchはcase/whenの強化版 — 全ケースを漏れなく処理

3

matchでデータ抽出と分岐を同時に行う

4

Option、ResultもEnum — Rustエラー処理の基盤

メリット

  • enumにバリアントを追加すると未処理箇所をコンパイラが全て見つけてくれる
  • データを持つenumはRubyのクラス継承より簡潔

デメリット

  • RubyのSymbolのように軽く使うにはボイラープレートが多い
  • matchの強制的な網羅マッチングが最初は面倒に感じる

ユースケース

状態マシン — 注文状態(待機/処理中/完了/キャンセル)を型で表現 APIレスポンス — 成功/エラー/タイムアウト各々に異なるデータを持つ時