RustのOptionとSomeを徹底理解:Null安全の実現
RustのOption列挙型、Some(T)、Noneを徹底解説:なぜRustにnullがないのか、match/if letによるパターンマッチ、コンビネータ活用法。
null の考案者であるトニー・ホーアは、かつて自身の発明を「10億ドルの過ち」と呼びました。Nullポインタの参照外しは、数え切れないほどのシステムクラッシュや脆弱性を引き起こしてきたからです。Rustは Option<T> 列挙型と Some(T) ヴァリアントを採用することで、null のリスクをコンパイル時に完全に根絶しました。
10歳でもわかる説明:謎のプレゼント箱#
宅配便で届いた包装された箱をイメージしてください:
Some(おもちゃ): 包装紙を開けると、中から本物のおもちゃが出てきます。None: 箱を開けたら完全に空っぽでした。
graph TD
Box["Option<T> (宅配の箱)"] --> Check{"中に物が入っているか?"}
Check -->|Yes: 存在| SomeBranch["Some(値)<br/>(型 T の実際のデータを保持)"]
Check -->|No: 空っぽ| NoneBranch["None<br/>(値が存在しない状態)"]
SomeBranch --> Unbox["match や if let で安全に箱を開封"]
NoneBranch --> Fallback["クラッシュさせずに安全にフォールバック処理"]
他のプログラミング言語では、空の箱であるにもかかわらず中身があると思い込んで操作(user.getName() など)してしまい、実行時エラーを引き起こします。Rustでは明示的に箱を開封(アンパック)しない限り、Some(T) の内部データを使用することはできません。
Option<T> の定義#
Option<T> は標準ライブラリのPreludeに組み込まれているため、宣言なしで直接使用できます:
enum Option<T> {
Some(T),
None,
}rustOption<T> と型 T はコンパイル時に明確に区別されるため、Option<i8> を通常の i8 と誤認して演算することは不可能です:
let x: i8 = 5;
let y: Option<i8> = Some(5);
// let sum = x + y; // ❌ コンパイルエラー: Option<i8> と i8 は加算不可rustSome() を扱う4つの実践的パターン#
1. match 式による完全なパターン網羅#
src/main.rs
fn get_user_avatar(user_id: u32) -> Option<String> {
if user_id == 42 {
Some(String::from("https://example.com/avatar.png"))
} else {
None
}
}
fn main() {
let avatar = get_user_avatar(42);
match avatar {
Some(url) => println!("アバターURL: {url}"),
None => println!("デフォルト画像を使用"),
}
}rust2. if let による簡潔な取り出し#
Some の場合のみ処理を行い、None は無視したい場合:
src/main.rs
let config_timeout: Option<u64> = Some(3000);
if let Some(timeout) = config_timeout {
println!("設定されたタイムアウト: {timeout}ms");
}rust3. デフォルト値の設定: unwrap_or と unwrap_or_else#
本番コードでは単なる .unwrap() を避け、フォールバック値を指定します:
src/main.rs
let port: Option<u16> = None;
// 即値でデフォルト値を指定
let active_port = port.unwrap_or(8080);
println!("ポート: {active_port}"); // 8080
// クロージャで遅延評価してデフォルト値を生成
let env_port = port.unwrap_or_else(|| {
4000 + 4000
});
println!("フォールバックポート: {env_port}");rust4. コンビネータによる関数型変換#
中身を取り出さずにチェーンメソッドでスマートに値を加工できます:
src/main.rs
let raw_input: Option<&str> = Some(" rustacean ");
let cleaned = raw_input
.map(|s| s.trim())
.filter(|s| !s.is_empty())
.map(|s| s.to_uppercase());
println!("{cleaned:?}"); // Some("RUSTACEAN")rustgraph LR
Input["Some(' rustacean ')"] -->|map trim| Trimmed["Some('rustacean')"]
Trimmed -->|filter not empty| Filtered["Some('rustacean')"]
Filtered -->|map uppercase| Final["Some('RUSTACEAN')"]
他言語のNull表現とRustの比較#
| 言語 | 表現方法 | 安全性レベル | チェック漏れ時の挙動 |
|---|---|---|---|
| JavaScript / TypeScript | null / undefined | 低 | 実行時 TypeError クラッシュ |
| Java / C# | null ポインタ | 中〜低 | NullPointerException |
| Python | None | 低 | AttributeError: 'NoneType' |
| Rust | Option<T> (Some / None) | コンパイル時100%安全 | コンパイルエラーで未処理を検知 |
まとめ#
Some(値)はOption<T>における有効な値の存在を表します。Noneは値の非存在を表します。- Rustは
nullをOption<T>に置き換えることで、Nullポインタによる実行時クラッシュを根絶しました。 - 網羅的チェックには
match、単一パターンにはif let、関数型チェーンには.map()や.unwrap_or()を活用します。