Option<T>Option<T> は「値があるかもしれないし、ないかもしれない」を
型レベルで強制的に扱わせる Rust の列挙型。null / None 参照例外を
コンパイル時に防ぐ FP の根幹機能。
Rust には null ポインタも None 参照例外もありません。
「値がないかもしれない」状況は必ず Option<T> という型で表現します。
呼び出し側は コンパイラから「None の場合を処理しろ」と強制される ため、
実行時の NullPointerException が原理的に発生しません。
これは「億ドルの過ち」と呼ばれる null の導入に対する Rust の解答です。
Tony Hoare(null を発明した本人)が 2009 年に「最悪の発明だった」と述懐したことで有名です。
Option<T> は標準ライブラリに定義された 列挙型(enum) です。
実装は驚くほどシンプルで、2 つのバリアント(変種)だけを持ちます。
// 標準ライブラリの定義(簡略)
pub enum Option<T> {
None, // 値なし
Some(T), // 値あり(T を包む)
}
T はジェネリック型パラメータです。Option<i32>、Option<String>、
Option<ElecData> など、任意の型を包める のでどんな文脈でも使えます。
// 使い方の基本
let found: Option<i32> = Some(42);
let missing: Option<i32> = None;
// 値を直接取り出すことは「できない」。必ず None を考慮させられる
let x = found + 1; // コンパイルエラー! Option に + は使えない
let x = found.unwrap() + 1; // コンパイルは通るが None でパニック(推奨しない)
| 特性 | null(Java / Python / C++) | Option<T>(Rust) |
|---|---|---|
| 「ないかも」の表現 | 全参照型が暗黙に null になれる | Option<T> を明示しない限り null にならない |
| 忘れたときの結果 | 実行時クラッシュ(NullPointerException) | コンパイルエラー(型が合わない) |
| 「あることが確定」の型 | なし(常に null の可能性がある) | T(Option なし)= 絶対に値がある |
| ドキュメント効果 | コメントに「null かも」と書くしかない | 型シグネチャが意図を宣言する |
| チェーン操作 | 毎回 null チェックが必要 | map / and_then / filter でチェーン |
unwrap() は最も単純ですが、None でパニックするため本番コードには使いません(テスト・プロトタイプは除く)。
代わりに以下のコンビネータ(メソッドチェーン)を使います。
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> | T | None のときデフォルト値を返す |
unwrap_or_else(f) | Option<T> | T | None のときクロージャを実行 |
ok_or(e) | Option<T> | Result<T, E> | None → Err に変換(? 演算子で使える) |
is_some() / is_none() | &Option<T> | bool | Some / None を真偽値で確認 |
as_ref() | &Option<T> | Option<&T> | 所有権を移動せず中身を参照 |
unwrap() | Option<T> | T | None でパニック。テスト以外は非推奨 |
コンビネータ以外に、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("値が見つかりません");
};
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);
}
elec: Option<ElecData> は「電気生理データが取れなかった(エラー)」
ではなく「この実験形式では電気生理を計測しない(正常なケース)」を表します。
null を使う言語では型を見ただけではどちらか判断できませんが、
Rust では型シグネチャが設計意図を明確に宣言します。
Python 版 ScanDataPy では同じ概念を暗黙の None や空の辞書で表現しており、
「電気生理があるかどうか」のチェックをいたるところに書く必要があります。
Rust 版ではコンパイラが強制するため、チェック漏れが原理的に起きません。
「値がないかも」は 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();
unwrap() は None のときにパニックします。本番コードでは
unwrap_or / map / if let / ok_or + ? のいずれかを使うこと。
例外: テストコード、またはコンパイル時に Some が確定する場合のみ許容
(それでも expect("理由") の方が推奨)。
get(&tag) -> Option<&V> の戻り値として Option が登場。ok_or での Result 変換例ありget(&tag) -> Option<&ValueObj> の Repository パターンOption<String> フィールドへの derive 適用例(Wrapper 構造体)