4 分で読了 #rust #type-system
このページの目次

enumとパターンマッチング

Rustのenumはタグ付き共用体(tagged union)であり、各variantは独立したデータを持ち、コンパイラがmatchで全てのvariantを網羅していることを保証します(網羅性チェック)。OptionとResultは言語組み込みではなく、標準ライブラリがenumを用いて定義しています——これは、enumがRustで最も表現力豊かな型構築子であることを証明しています。

enumはtagged unionであり、単なる「列挙」ではない

Cのenumは単なる名前付き整数定数の集合に過ぎません。Rustのenumの各variantは異なるデータを持てる——それはタグ付き共用体です:

enum Message {
    Quit,                            // データなし — 純粋なタグ
    Move { x: i32, y: i32 },         // 匿名struct
    Write(String),                   // タプルvariant
    ChangeColor(u8, u8, u8),         // u8が3つのタプル
}

コンパイラは Message に対して、最大のvariant(Move = i32が2つ = 8バイト)を格納するのに十分なサイズを割り当て、さらにdiscriminant(タグ)フィールドを追加します。Option<&T> のようなnicheが存在するケースでは、タグの最適化が行われます。

Option: Rustにnullはない

Tony Hoareはnullを「10億ドルのミス」と呼んでいます。Rustにはnullがありません——存在しないかもしれない値は Option<T> で表されます:

fn safe_div(a: i32, b: i32) -> Option<i32> {
    if b == 0 { None } else { Some(a / b) }
}
// 呼び出し側はNoneの場合を処理しなければならない:
match safe_div(10, 0) {
    Some(result) => println!("{}", result),
    None => println!("division by zero"),
}

コンパイラは None の場合の処理を強制します——チェックを忘れることは不可能です。Option<T> は特殊なnullマーカではなく通常の型なので、Option に対してmap、and_thenなどのcombinatorを使用できます。

Result: エラーも値である

例外の問題は、制御フローを壊してしまうことです——関数のシグネチャからは、どのような例外を投げる可能性があるか分かりません。Result<T, E> はエラーを戻り値の一部にします:

fn parse(s: &str) -> Result<i32, std::num::ParseIntError> {
    s.parse()                        // Resultを返す。throwではない
}

呼び出し側は Result<i32, ParseIntError> を見ることで——この関数は成功時に i32 を返し、失敗時に解析エラーを返す可能性がある——ことが分かります。隠れた制御フローはありません。

Match: コンパイル時の網羅性チェック

match number {
    1 => "one",
    2 => "two",
    // _ => "other",                 // この行が欠けている場合、コンパイルエラー:
                                     // "non-exhaustive patterns"
}

コンパイラは、matchが全ての可能なvariantを網羅しているかどうかをチェックします。型に新しいvariantが追加された場合(例えばenumに新しいメンバーが追加された場合)、その型に対するmatchが書かれている箇所はすべてコンパイルに失敗します——新しいケースを明示的に処理することを強制します。

パターンの様々な形式

// デストラクチャリング
let (x, y, z) = (1, 2, 3);
let Point { x, y } = point;

// refパターン: moveせず、借用する
let ref r = value;                   // r: &T, valueはmoveされない

// match guard: 追加の条件
match num {
    n if n % 2 == 0 => "even",
    n if n > 10 => "big",
    _ => "other",
}

// @バインディング: パターンと変数バインディングを同時に実行
match opt {
    Some(v @ 1..=5) => println!("small: {}", v),
    Some(v) => println!("large: {}", v),
    None => (),
}

// if let / while let: 1つのvariantのみに関心がある場合のシンタックスシュガー
if let Some(v) = opt { process(v); }
while let Some(v) = iter.next() { process(v); }

参考

  • Rust Book: 第6章 (Enums)、第18章 (Patterns)
  • Rust Reference: Match expressions, Patterns

Keywords: enum, Option, Result, match, pattern, if let, while let, exhaustiveness, tagged union