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 バリアントに関連付けられていることを示します。

前のセクションで説明したように、NoneSome は型ではなく、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 式のすべての定型コードが不要になることです。

unwrapexpect を使用する

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");