あっぽログ
← 記事一覧に戻る

RustのEnumとパターンマッチングを使いこなす:Option・matchから複雑な構造体まで

RustのEnumとは?

RustのEnum(列挙型)は、複数の種類の値をひとつの型として表現できる強力な機能です。
他の言語のEnumと異なり、Rustのenumは各バリアント(種類)にデータを持たせることができます。これにより、OptionResultといった標準ライブラリの中核を担う型も、Enumとして実装されています。

まずシンプルな例から見てみましょう。

enum Direction {
    North,
    South,
    East,
    West,
}

fn main() {
    let dir = Direction::North;
}

これだけなら他言語と変わりませんが、Rustではバリアントにデータを付加できます。

enum Message {
    Quit,                       // データなし
    Move { x: i32, y: i32 },   // 名前付きフィールド
    Write(String),              // タプル構造体風
    ChangeColor(u8, u8, u8),    // 複数の値
}

Message::Moveは座標を持ち、Message::Writeは文字列を持ちます。一つの型でこれだけ多様な表現が可能なのがRustのEnumの強みです。


match式でパターンマッチング

Enumを活用するにはmatch式が欠かせません。matchはすべてのバリアントを網羅的に処理することを強制するため、処理漏れをコンパイル時に防げます。

fn handle_message(msg: Message) {
    match msg {
        Message::Quit => {
            println!("終了します");
        }
        Message::Move { x, y } => {
            println!("({}, {}) に移動します", x, y);
        }
        Message::Write(text) => {
            println!("メッセージ: {}", text);
        }
        Message::ChangeColor(r, g, b) => {
            println!("色を ({}, {}, {}) に変更します", r, g, b);
        }
    }
}

バリアントに応じてデータを取り出しながら処理を分岐できます。もし一つでも処理を書き忘れると、コンパイルエラーになるので安全です。

_(ワイルドカード)でその他をまとめる

すべてのケースを個別に書かずに、残りをまとめて処理したい場合は_を使います。

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

fn value(coin: Coin) -> u32 {
    match coin {
        Coin::Quarter => 25,
        _ => 1, // Quarter以外はすべて1
    }
}

Option型を理解する

Rustにはnullがありません。代わりに値があるかないかを表現するためにOption<T>というEnumが用意されています。

enum Option<T> {
    Some(T), // 値がある
    None,    // 値がない
}

たとえばリストの先頭要素を取り出す関数は、要素がない場合も考慮してこう書けます。

fn first_element(list: &[i32]) -> Option<i32> {
    if list.is_empty() {
        None
    } else {
        Some(list[0])
    }
}

fn main() {
    let numbers = vec![10, 20, 30];
    match first_element(&numbers) {
        Some(n) => println!("先頭の値: {}", n),
        None    => println!("リストは空です"),
    }

    let empty: Vec<i32> = vec![];
    match first_element(&empty) {
        Some(n) => println!("先頭の値: {}", n),
        None    => println!("リストは空です"),
    }
}

出力:

先頭の値: 10
リストは空です

if letで簡潔に書く

matchを使わず、特定のバリアントだけ処理したい場合はif letが便利です。

let some_value: Option<i32> = Some(42);

if let Some(v) = some_value {
    println!("値は {}", v);
}

Noneの場合は何もしない、という意図が明確に伝わります。


Enumにメソッドを実装する

Enumにもimplブロックを使ってメソッドを追加できます。

enum TrafficLight {
    Red,
    Yellow,
    Green,
}

impl TrafficLight {
    fn duration_secs(&self) -> u32 {
        match self {
            TrafficLight::Red    => 60,
            TrafficLight::Yellow => 5,
            TrafficLight::Green  => 45,
        }
    }

    fn is_stop(&self) -> bool {
        matches!(self, TrafficLight::Red)
    }
}

fn main() {
    let light = TrafficLight::Red;
    println!("待ち時間: {} 秒", light.duration_secs()); // 60 秒
    println!("停止が必要: {}", light.is_stop());         // true
}

matches!マクロは、特定のパターンかどうかをboolで返す便利なマクロです。


まとめ

機能用途
enum複数種の値を一つの型で表現
match網羅的なパターンマッチング
Option<T>null安全な「値あり/なし」の表現
if let特定バリアントだけを簡潔に処理
impl on enumEnumへのメソッド追加

RustのEnumとパターンマッチングは、型安全性を保ちながら複雑なロジックを整理するための核心的な機能です。OptionResult(エラーハンドリング)もすべてEnumで構成されており、これをマスターするとRustコード全体の読み書きが格段にスムーズになります。ぜひ積極的に活用してみてください!

← 記事一覧に戻る