Rust の Option<T>

質問: Option<T> 型とは何か? — 作成日: 2026-05-18
一言要約: Option<T> は「値があるかもしれないし、ないかもしれない」を 型レベルで強制的に扱わせる Rust の列挙型。null / None 参照例外を コンパイル時に防ぐ FP の根幹機能。
目次
  1. Option<T> とは
  2. 構造: Some と None
  3. null との比較
  4. コンビネータ一覧
  5. パターンマッチ
  6. scandata 文脈: elec: Option<ElecData>
  7. Option と Result の使い分け
  8. 関連項目

1. Option<T> とは

Rust には null ポインタも None 参照例外もありません。 「値がないかもしれない」状況は必ず Option<T> という型で表現します。 呼び出し側は コンパイラから「None の場合を処理しろ」と強制される ため、 実行時の NullPointerException が原理的に発生しません。

これは「億ドルの過ち」と呼ばれる null の導入に対する Rust の解答です。 Tony Hoare(null を発明した本人)が 2009 年に「最悪の発明だった」と述懐したことで有名です。

null の世界(Java / Python / C++) String value null 値あり 爆発する可能性 NullPointerException は実行時まで気づけない Option の世界(Rust) Some("hello") 値あり None 値なし(型で明示) コンパイラが「None を処理しろ」と強制 実行時クラッシュなし
図 1. null(実行時爆弾)vs Option(コンパイル時安全性)の対比

2. 構造: Some と None

Option<T> は標準ライブラリに定義された 列挙型(enum) です。 実装は驚くほどシンプルで、2 つのバリアント(変種)だけを持ちます。

// 標準ライブラリの定義(簡略)
pub enum Option<T> {
    None,           // 値なし
    Some(T),        // 値あり(T を包む)
}

T はジェネリック型パラメータです。Option<i32>Option<String>Option<ElecData> など、任意の型を包める のでどんな文脈でも使えます。

Some(T) タグ: Some T(実際の値) Some("hello") Some(42) Some(ElecData{...}) None タグ: None のみ (データを持たない) ゼロコスト。サイズは T と同程度
図 2. Option<T> のメモリレイアウトイメージ。None はタグだけ、Some は T を包む
// 使い方の基本
let found: Option<i32> = Some(42);
let missing: Option<i32> = None;

// 値を直接取り出すことは「できない」。必ず None を考慮させられる
let x = found + 1;   // コンパイルエラー! Option に + は使えない
let x = found.unwrap() + 1;  // コンパイルは通るが None でパニック(推奨しない)

3. null との比較

特性 null(Java / Python / C++) Option<T>(Rust)
「ないかも」の表現 全参照型が暗黙に null になれる Option<T> を明示しない限り null にならない
忘れたときの結果 実行時クラッシュ(NullPointerException) コンパイルエラー(型が合わない)
「あることが確定」の型 なし(常に null の可能性がある) T(Option なし)= 絶対に値がある
ドキュメント効果 コメントに「null かも」と書くしかない 型シグネチャが意図を宣言する
チェーン操作 毎回 null チェックが必要 map / and_then / filter でチェーン

4. コンビネータ一覧

unwrap() は最も単純ですが、None でパニックするため本番コードには使いません(テスト・プロトタイプは除く)。 代わりに以下のコンビネータ(メソッドチェーン)を使います。

Option<T> map(|v| f(v)) Some(T) → Some(U) None → None and_then(|v| ...) = flatMap 連鎖する Option unwrap_or(default) None のとき デフォルト値を使う filter(|v| pred(v)) 条件不成立 → None Option<U> Option<U> T(確定値) Option<T> 変換(型が変わる) 連鎖(Option 返す関数を続ける) デフォルト(Option を脱出) 絞り込み
図 3. 主要コンビネータの入出力フロー
let name: Option<String> = Some("hello".to_owned());

// map: Some の中の値を変換。None はそのまま通過
let upper: Option<String> = name.map(|s| s.to_uppercase());

// and_then: Option を返す関数を繋げる(flatMap)
let parsed: Option<u32> = name
    .as_deref()
    .and_then(|s| s.parse::<u32>().ok());

// unwrap_or: None のときデフォルト値を返す
let val: u32 = parsed.unwrap_or(0);

// ok_or: Option → Result に変換(? 演算子で使える)
fn get_value(opt: Option<i32>) -> Result<i32, String> {
    opt.ok_or("値が存在しません".to_owned())
}

// ? 演算子で伝播(Result 化してから)
let v = opt.ok_or(MyError::NotFound)?;
メソッド入力出力用途
map(f)Option<T>Option<U>Some 内の値を変換。None は無視
and_then(f)Option<T>Option<U>Option を返す関数を連鎖(flatMap)
filter(pred)Option<T>Option<T>条件不成立なら None に変換
unwrap_or(v)Option<T>TNone のときデフォルト値を返す
unwrap_or_else(f)Option<T>TNone のときクロージャを実行
ok_or(e)Option<T>Result<T, E>None → Err に変換(? 演算子で使える)
is_some() / is_none()&Option<T>boolSome / None を真偽値で確認
as_ref()&Option<T>Option<&T>所有権を移動せず中身を参照
unwrap()Option<T>TNone でパニック。テスト以外は非推奨

5. パターンマッチ

コンビネータ以外に、match 式や if let で直接分岐する方法もあります。

// match: 全ケースを網羅しないとコンパイルエラー
match some_option {
    Some(v) => println!("値: {v}"),
    None    => println!("値なし"),
}

// if let: Some の場合だけ処理(None は else に飛ぶ)
if let Some(v) = some_option {
    println!("値: {v}");
}

// while let: イテレータ的に使う
while let Some(item) = stack.pop() {
    println!("{item}");
}

// let-else (Rust 1.65+): None なら早期 return
let Some(v) = some_option else {
    return Err("値が見つかりません");
};

6. scandata 文脈: elec: Option<ElecData>

scandata の ScanRecord 構造体(設計上の中核データ型)では、 電気生理データ ElecData常に存在するとは限らない ために Option を使います。蛍光イメージングのみの実験では電気生理チャンネルを 接続しないこともあるため、これは「プログラムのバグ」ではなく ドメインの実態を型で正確に表現している 例です。

// ScanRecord の設計意図(概念)
pub struct ScanRecord {
    pub fluo: FluoData,           // 蛍光データ: 必ず存在する
    pub elec: Option<ElecData>,   // 電気生理: あるかもしれない
    pub meta: FileMeta,           // ファイルメタ: 必ず存在する
}

// 使い側: None を無視して処理(コンビネータ)
let elec_trace = record.elec
    .as_ref()
    .map(|e| e.to_trace());    // 電気生理がなければ None のまま

// 使い側: 存在確認してから処理
if let Some(elec) = &record.elec {
    plot_electrophysiology(elec);
}
ScanRecord fluo: FluoData 常に Some(型に Option なし) elec: Option<ElecData> TSM のみ実験では None meta: FileMeta 常に Some(型に Option なし) 必ず存在 オプション(あるかもしれない) 実験タイプ別の状態 蛍光イメージングのみ fluo: FluoData(...) elec: None 電気生理なし → Option<ElecData> = None 蛍光 + 電気生理同時記録 fluo: FluoData(...) elec: Some(ElecData{...}) 両方存在 → Option<ElecData> = Some
図 4. ScanRecord における Option<ElecData> の役割。ドメインの実態を型で表現する
設計の意図: elec: Option<ElecData> は「電気生理データが取れなかった(エラー)」 ではなく「この実験形式では電気生理を計測しない(正常なケース)」を表します。 null を使う言語では型を見ただけではどちらか判断できませんが、 Rust では型シグネチャが設計意図を明確に宣言します。

Python 版 ScanDataPy では同じ概念を暗黙の None や空の辞書で表現しており、 「電気生理があるかどうか」のチェックをいたるところに書く必要があります。 Rust 版ではコンパイラが強制するため、チェック漏れが原理的に起きません。

7. Option と Result の使い分け

「値がないかも」は Option、「失敗するかも」は Result が適切です。 両者は相互変換できます。

Option<T>Result<T, E>
意味 値があるかもしれない 成功か、失敗(理由付き)か
失敗情報 なし(None は理由を持たない) Err(E) に詳細を含める
典型例 HashMap の検索、オプショナルフィールド ファイル I/O、パース、ネットワーク
? 演算子 関数が Option を返す場合のみ 関数が Result を返す場合のみ
相互変換 .ok_or(err) で Result 化 .ok() で Option 化(Err は None に)
// Option → Result: ? で伝播できるようになる
fn lookup(map: &HashMap<String, i32>, key: &str) -> Result<i32, MyError> {
    map.get(key)
       .copied()
       .ok_or(MyError::KeyNotFound(key.to_owned()))
}

// Result → Option: エラー詳細が不要なとき
let num: Option<i32> = "42".parse::<i32>().ok();
アンチパターン: Option に unwrap() を使う
unwrap() は None のときにパニックします。本番コードでは unwrap_or / map / if let / ok_or + ? のいずれかを使うこと。 例外: テストコード、またはコンパイル時に Some が確定する場合のみ許容 (それでも expect("理由") の方が推奨)。

目次に戻る