コンテンツにスキップ

RustのOption

Rustには null がありません。値がないかもしれないときは、Option を使って型そのもので表します。最初は少し手間に感じるかもしれませんが、これによって、ほかの言語で数えきれないほどのクラッシュを引き起こしてきた「null参照」というバグを、まるごと防げるのです。

Option は、2つのバリアントを持つ列挙型(enum)です。

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

実は、もう見たことがあるはずです。ベクタの .get() も、HashMapの .get() も Option を返します。

fn main() {
let has_value: Option<i32> = Some(5);
let no_value: Option<i32> = None;
println!("{:?}", has_value);
println!("{:?}", no_value);
}

実行結果:

Some(5)
None
▶ 実際に試してみよう

値を取り出す安全な方法は match です。Rustは両方のケースを処理するよう強制するので、「値がない」ケースをうっかり忘れることがありません。

fn main() {
let age: Option<i32> = Some(30);
match age {
Some(value) => println!("年齢は{value}歳です"),
None => println!("年齢が入力されていません"),
}
}
▶ 実際に試してみよう
fn find_user(id: u32) -> Option<&'static str> {
if id == 1 {
Some("Alice")
} else {
None
}
}
fn main() {
match find_user(1) {
Some(name) => println!("見つかりました: {name}"),
None => println!("ユーザーが見つかりません"),
}
match find_user(99) {
Some(name) => println!("見つかりました: {name}"),
None => println!("ユーザーが見つかりません"),
}
}

実行結果:

見つかりました: Alice
ユーザーが見つかりません
▶ 実際に試してみよう

ユーザーが見つからないときに null を返したり例外を投げたりする代わりに、関数の戻り値の型そのものが、呼び出す側全員に「何も見つからないかもしれません」と伝えています。そして、コンパイラはその可能性を無視させてくれません。

簡単な場面では、毎回 match を書くのは大げさに感じるかもしれません。そんなときの近道がいくつかあります。

fn main() {
let age: Option<i32> = None;
let value = age.unwrap_or(0); // 値がなければ0を使う
println!("{value}");
}
▶ 実際に試してみよう

.unwrap() というメソッドもあります。値を直接取り出せますが、None だった場合はパニック(プログラムが停止)します。

let age: Option<i32> = Some(30);
let value = age.unwrap(); // ここではageがSomeなので問題なし

スライスの中で最初に見つかった偶数を返し、なければ None を返す fn first_even(numbers: &[i32]) -> Option<i32> を書いてください。[3, 7, 8, 10] の結果は match で、[1, 3, 5] の結果は .unwrap_or(0) で表示しましょう。ヒント:スライスをループして、n % 2 == 0 になった時点で return Some(n) します。ループを抜けたら None を返します。

解答を見る
fn first_even(numbers: &[i32]) -> Option<i32> {
for &n in numbers {
if n % 2 == 0 {
return Some(n);
}
}
None
}
fn main() {
match first_even(&[3, 7, 8, 10]) {
Some(n) => println!("最初の偶数: {n}"),
None => println!("偶数はありません"),
}
let fallback = first_even(&[1, 3, 5]).unwrap_or(0);
println!("代わりの値を使うと: {fallback}");
}

実行結果:

最初の偶数: 8
代わりの値を使うと: 0
▶ 実際に試してみよう

"音量" が 80 に設定されているだけの、設定用の HashMap を作ってください。そのあと "音量" と "明るさ" の2つのキーをループして、値があればそれを、なければ「設定されていません」というメッセージを表示します。ヒント:HashMapの .get(key) は Option を返すので、そのまま match できます。

解答を見る
use std::collections::HashMap;
fn main() {
let mut settings = HashMap::new();
settings.insert("音量", 80);
for key in ["音量", "明るさ"] {
match settings.get(key) {
Some(value) => println!("{key} = {value}"),
None => println!("{key}は設定されていません"),
}
}
}

実行結果:

音量 = 80
明るさは設定されていません
▶ 実際に試してみよう
  • Option<T> は、あるかもしれないし、ないかもしれない値を表します。Rustには null がありません。
  • Some(value) は値を持っていて、None は何もないことを意味します。
  • match は両方のケースを扱う安全な方法です。コンパイラがケースの書き忘れを許しません。
  • .unwrap_or(default) は代わりの値を返します。.unwrap() は値が None だとパニックするので、使いすぎないようにしましょう。

理解度チェック

1. 値がないかもしれないとき、Rustはnullの代わりに何を使いますか?

2. None の値に対して .unwrap() を呼ぶとどうなりますか?

3. age が None のとき、age.unwrap_or(0) はどうなりますか?

4. match がOptionから値を取り出す安全な方法とされるのはなぜですか?

5. このページで紹介したもののうち、すでにOptionを返すメソッドはどれですか?

スコア: 0 / 5