Option<T> — nilが消えた世界で生き残る

Rubyのnilと&.演算子がRustでどう変わるか

Rubyではnilは空気のような存在だ。配列の存在しないインデックスを参照するとnil、Hashの存在しないキーを参照するとnil、メソッドが明示的に返さなければnil。そしてnilにメソッドを呼ぶとNoMethodError。本番エラーのかなりの部分がここから来る。

Rustはnilを完全に排除した。代わりにOption<T>というenumを使う。

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

Rubyとの比較

# Ruby
def find_user(id)
  users[id]  # nilかもしれない
end
user = find_user(99)
user.name  # nilならNoMethodError!

// Rust
fn find_user(id: u32) -> Option<User> {
    // Some(user)またはNone
}
let user = find_user(99);
// user.name  // コンパイルエラー!Option<User>にnameメソッドはない

RustはOption<User>User別の型として扱う。Optionから値を取り出して初めて使える。

値の取り出し — unwrap(危険)

let user = find_user(99).unwrap();  // Noneならpanic!

Rubyでnilにメソッドを呼ぶのと同程度の危険。本番コードでは避けるべき。

安全な取り出し — match, if let, map

// match
match find_user(99) {
    Some(user) => println!("{}", user.name),
    None => println!("ユーザーなし"),
}

// if let(1つだけチェック)
if let Some(user) = find_user(99) {
    println!("{}", user.name);
}

// map — Rubyの&.に最も近い
let name = find_user(99).map(|u| u.name);
// Option<String>を返す

&. → map/and_then 対応表

Rubyのsafe navigationとOptionメソッドの対応:

# Ruby
user&.name              # nilならnil返却
user&.address&.city     # チェーン
user&.name || "unknown" # デフォルト値

// Rust
user.map(|u| u.name)                          // NoneならNone
user.and_then(|u| u.address).map(|a| a.city)  // チェーン
user.map(|u| u.name).unwrap_or("unknown")     // デフォルト値

mapSome内の値を変換する。and_thenは変換結果がまたOptionの場合に使う(flatMap)。unwrap_orNoneの時のデフォルト値。

unwrap_or_else — デフォルト値が高コストな場合

let name = find_user(99)
    .map(|u| u.name)
    .unwrap_or_else(|| fetch_default_name()); // Noneの時だけ実行

Rubyの||user&.name || expensive_default)は右辺を必要な時だけ評価する。Rustのunwrap_or_elseも同じ遅延評価。

キーポイント

1

Option<T>はSome(値)またはNone — nilの型安全な代替

2

unwrapは危険 — match、if let、mapで安全に処理

3

&. → map、チェーン → and_then、デフォルト値 → unwrap_or

4

Option<User>とUserは別の型 — 必ず取り出して使用

メリット

  • nilに対するNoMethodErrorがそもそも不可能 — コンパイル時に検出
  • None処理を漏らすとコンパイルが通らない

デメリット

  • Rubyのようにnilを無視して進めないので最初は面倒
  • Optionチェーンが長くなると可読性が落ちることがある

ユースケース

DBクエリ結果が存在しない可能性がある時 — find vs find_by 設定値が存在するかもしれないし存在しないかもしれない時

参考資料