Option 型を使用して値がない場合に対処する
Rust 標準ライブラリには、値が存在しない可能性がある場合に使用する Option<T>
列挙型が用意されています。 Option<T>
は、Rust コードで広く使用されています。 これは、存在する場合と空の場合がある値を操作するのに便利です。
他の多くの言語では、値が存在しないことは null
または nil
を使用してモデル化されますが、Rust では他の言語と相互運用できるコードの外部で null
は使用されません。 Rust は、値が省略可能である場合について明示的です。 多くの言語で、String
を受け取る関数は、実際には String
または null
のいずれかを受け取る可能性がありますが、Rust では、同じ関数で受け取れるのは、実際の String
だけです。 Rust で省略可能な文字列をモデル化する場合は、Option
型でそれを明示的にラップする必要があります: Option<String>
。
Option<T>
は、2 つのバリアントのどちらかになります。
enum Option<T> {
None, // The value doesn't exist
Some(T), // The value exists
}
Option<T>
列挙型の宣言の <T>
の部分は、型 T
がジェネリックであり、Option
列挙型の Some
バリアントに関連付けられていることを示します。
前のセクションで説明したように、None
と Some
は型ではなく、Option<T>
型のバリアントです。これは特に、関数が引数として Some
または None
を取ることはできず、Option<T>
のみを受け取ることを意味します。
前のユニットでは、ベクターの存在しないインデックスにアクセスしようとすると、プログラムが panic
になるということを説明しましたが、Vec::get
メソッドを使用することでそれを回避できます。このメソッドを使用すると、パニックにならずに Option
型が返されます。 指定したインデックスに値が存在する場合、それは Option::Some(value)
バリアントにラップされます。 インデックスが範囲外の場合は、代わりに Option::None
値が返されます。
試しに使ってみましょう。 次のコードは、ローカル環境で実行することも、Rust Playground で実行することもできます。
let fruits = vec!["banana", "apple", "coconut", "orange", "strawberry"];
// pick the first item:
let first = fruits.get(0);
println!("{:?}", first);
// pick the third item:
let third = fruits.get(2);
println!("{:?}", third);
// pick the 99th item, which is non-existent:
let non_existent = fruits.get(99);
println!("{:?}", non_existent);
出力は次のようになります。
Some("banana")
Some("coconut")
None
出力されたメッセージを見ると、fruits
配列内の既存のインデックスにアクセスする最初の 2 回の試行は Some("banana")
および Some("coconut")
という結果になりましたが、99 番目の要素をフェッチしようとすると、パニックにはならずに None
値 (どのようなデータにも関連付けられていない) が返されたことがわかります。
実際には、取得した列挙型バリアントに応じてプログラムの動作を決定する必要があります。 しかし、Some(data)
バリアント内のデータにアクセスするにはどうすればよいでしょうか。
パターン マッチング
Rust には match
と呼ばれる強力な演算子があります。 これを使用してパターンを指定すると、プログラムのフローを制御できます。 match
によって一致するパターンが検出されると、指定したコードがそのパターンを使って実行されます。
let fruits = vec!["banana", "apple", "coconut", "orange", "strawberry"];
for &index in [0, 2, 99].iter() {
match fruits.get(index) {
Some(fruit_name) => println!("It's a delicious {}!", fruit_name),
None => println!("There is no fruit! :("),
}
}
この例を Rust Playground で実行してみることができます。
出力は次のようになります。
It's a delicious banana!
It's a delicious coconut!
There is no fruit! :(
上のコードでは、前の例と同じインデックス (0、2、99) を反復処理し、それぞれで fruits.get(index) 式を使用して fruits
ベクターから値を取得します。
fruits
ベクターには &str
要素が含まれているため、この式の結果は Option<&str>
型であることがわかります。 次に、Option
の値に対して match 式を使用し、各バリアントに対する一連のアクションを定義します。 Rust ではこれらの分岐を "match アーム" と呼びます。各アームで、一致した値に対する 1 つの可能な結果を処理できます。
最初のアームでは、新しい変数の fruit_name
が導入されています。 この変数は、Some
値内のすべての値と一致します。 fruit_name
のスコープは match 式に限定されるので、match
に導入する前に fruit_name
を宣言しても意味がありません。
Some
バリアント内の値に応じて異なる処理を行うように、match 式をさらに調整できます。 たとえば、次のコードを実行すると、ココナッツはすばらしいという事実を強調することができます。
let fruits = vec!["banana", "apple", "coconut", "orange", "strawberry"];
for &index in [0, 2, 99].iter() {
match fruits.get(index) {
Some(&"coconut") => println!("Coconuts are awesome!!!"),
Some(fruit_name) => println!("It's a delicious {}!", fruit_name),
None => println!("There is no fruit! :("),
}
}
Note
一致の最初のパターンは Some(&"coconut")
です (文字列リテラルの前の &
に注意してください)。 これは、fruits.get(index)
が Option<&&str>
または文字列スライスへの参照のオプションを返すためです。
パターン内で &
を削除することは、Option<&str>
(文字列スライスへの省略可能な参照 "ではなく"、省略可能な文字列スライス) との照合を試みていることを意味します。 現時点では、これが完全に意味を持たない可能性があるため、参照については説明しませんでした。 今のところは、&
によって型が適切に整列されることだけを覚えておいてください。
この例を Rust Playground で実行してみることができます。
出力は次のようになります。
It's a delicious banana!
Coconuts are awesome!!!
There is no fruit! :(
文字列値が "coconut"
の場合は、最初のアームが一致し、実行フローを決定するために使用されます。
match 式を使用する場合は常に、次の規則に注意してください。
match
アームは、上から下に評価されます。 一般的なケースより前に、限定的なケースを定義する必要があります。そうしないと、限定的なケースが一致せず、評価されません。match
アームは、入力の型で可能なすべての値をカバーする必要があります。 完全でないパターン リストと照合しようとすると、コンパイラ エラーが発生します。
if let 式
Rust には、値が 1 つのパターンに準拠しているかどうかをテストする便利な方法が用意されています。
次の例では、match
への入力は Option<u8>
値になります。 match
式では、その入力値が 7 の場合にのみコードが実行されます。
let a_number: Option<u8> = Some(7);
match a_number {
Some(7) => println!("That's my lucky number!"),
_ => {},
}
この場合、None
バリアントと Some(7)
と一致しないすべての Some<u8>
値を無視します。 ワイルドカード パターンは、このような状況に適しています。 他のすべてのパターンの後に _
(アンダースコア) ワイルドカード パターンを追加して、"それ以外のすべて" と一致させることができます。それを使用して、match アームを網羅するというコンパイラの要求を満たします。
このコードを統合するには、if let 式を使用します。
let a_number: Option<u8> = Some(7);
if let Some(7) = a_number {
println!("That's my lucky number!");
}
if let 演算子を使用すると、パターンと式が比較されます。 式とパターンが一致した場合、if ブロックが実行されます。 if let 式が便利なのは、1 つのパターンに一致させる必要がある場合に、match 式のすべての定型コードが不要になることです。
unwrap
と expect
を使用する
unwrap
メソッドを使用すると、Option
型の内部の値に直接アクセスすることができます。 ただし、バリアントが None
の場合、このメソッドはパニックになるため、注意してください。
次に例を示します。
let gift = Some("candy");
assert_eq!(gift.unwrap(), "candy");
let empty_gift: Option<&str> = None;
assert_eq!(empty_gift.unwrap(), "candy"); // This will panic!
この場合、コードは次の出力でパニックになります。
thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', src/main.rs:6:27
expect
メソッドは unwrap
と同じように動作しますが、2 番目の引数で指定するカスタム パニック メッセージが提供されます。
let a = Some("value");
assert_eq!(a.expect("fruits are healthy"), "value");
let b: Option<&str> = None;
b.expect("fruits are healthy"); // panics with `fruits are healthy`
出力は次のようになります。
thread 'main' panicked at 'fruits are healthy', src/main.rs:6:7
これらの関数はパニックになる可能性があるため、使用しないことをお勧めします。 代わりに、次のいずれかの方法を検討してください。
- パターン マッチングを使用し、
None
のケースを明示的に処理します。 unwrap_or
のような、パニックにならない同様のメソッドを呼び出します。このメソッドを使用すると、バリアントがNone
の場合は既定値が返され、バリアントがSome(value)
の場合は内部値が返されます。
assert_eq!(Some("dog").unwrap_or("cat"), "dog");
assert_eq!(None.unwrap_or("cat"), "cat");