Option を使って欠損値を扱う

RustBeginner
オンラインで実践に進む

はじめに

実際の検索では、必ずしも値が見つかるとは限りません。Rust では、この可能性を Option<T> で表します。Some(value) は値が存在することを示し、None は値が存在しないことを示します。これにより、存在しない状態を適当な数値で表したり、パニックを起こす危険に頼ったりせず、値の不在を型の一部として扱えます。

この実験では、小さな在庫レポーターを完成させます。見つかった在庫と見つからない在庫を処理し、在庫数が 0 の既存レコードとレコード自体が存在しない状態を区別します。また、値が存在する場合だけ処理が必要なときに if let を使用します。

存在する値と存在しない値を match する

このステップでは、プレースホルダーのレポートを、検索結果の Option に対する網羅的な match に置き換えます。

用意されている Cargo プロジェクトに移動します。

cd /home/labex/project/option-inventory

ソースファイルを開きます。

nano src/main.rs

用意されている find_stock 関数は Option<u32> を返します。ここでは山括弧を「u32 を格納する可能性がある Option」と読み取ってください。山括弧のパラメーターを使って再利用可能な型を定義する方法は、後の実験で学びます。Some(12) は在庫数を格納し、None は在庫数をまったく格納しません。

用意されている検索処理の末尾にある _ => None では、ワイルドカードパターン _ を使用しています。これは「前のアームで一致しなかったすべてのコード」を意味します。ワイルドカードは使用可能な変数を作りません。一致しなかった値を意図的に無視し、None を返します。

report 内にあるプレースホルダーの println! を見つけ、その 1 行を次の match に置き換えます。

    match find_stock(code) {
        Some(count) => println!("{code}: {count} in stock"),
        None => println!("{code}: not found"),
    }

Some(count) は値が存在することを判定すると同時に、内部の数値にローカル名 count を付けます。None アームは値が存在しない場合を明示的に処理するため、この match は Option<u32> のすべての可能な値を扱います。

Ctrl+O で保存し、Enter を押してから、Ctrl+X で終了します。実行する前にプログラムを確認します。

cargo check

Finished と表示されれば、Option の両方のケースを処理できています。次に、レポーターを実行します。

cargo run --quiet

次のように表示されます。

A1: 12 in stock
B2: 0 in stock
Z9: not found

最後の行は None から出力されます。最初の 2 行は Some から出力されており、Some(0) の値も含まれます。

0 と値の不在を区別する

このステップでは、在庫数が 0 の既存レコードを、0 を「見つからない」として扱わず、独自に処理します。

Some(0) と None は異なる意味を持ちます。Some(0) は、検索によって在庫数が 0 のレコードが見つかったことを示します。None は、そのコードのレコードが存在しないことを示します。この違いがあるため、0 のようなプレースホルダーで値の不在を安全に代用することはできません。

もう一度ソースファイルを開きます。

nano src/main.rs

report 内の match で、既存の Some(count) アームのすぐ上に、次のアームを追加します。

        Some(0) => println!("{code}: out of stock"),

match のアームは上から順番に確認されます。より広いパターンである Some(count) は 0 にも一致するため、具体的な Some(0) パターンを先に置く必要があります。

保存して終了し、プログラムを実行します。

cargo run --quiet

今度は次のように表示されます。

A1: 12 in stock
B2: out of stock
Z9: not found

これで各行が、在庫あり、レコードは存在するが在庫切れ、レコードなしという異なる状態を表すようになりました。

値が存在する場合だけ処理する

このステップでは、検索が成功した場合だけ処理するタスクに if let を使用します。

すべてのケースで異なる処理が必要な場合は、網羅的な match が適しています。一方、if let PATTERN = VALUE は、もう 1 つの一般的な状況に適した簡潔な条件付きパターンです。パターンが一致した場合だけ 1 つのブロックを実行し、一致しなければ何もしません。

ソースファイルを開きます。

nano src/main.rs

main のすぐ上に、次の関数を追加します。

fn highlight_available(code: &str) {
    if let Some(count) = find_stock(code) {
        println!("{code}: {count} available for promotion");
    }
}

このパターンは Some の場合だけ一致するため、波括弧の中で count を使用できます。None の場合にこのヘルパー関数が意図的に何も表示しないため、else ブロックはありません。

main で、用意されている highlight_available("C3"); の行から先頭の // を削除します。保存して終了したら、確認と実行を行います。

cargo check
cargo run --quiet

完全な出力は次のようになります。

A1: 12 in stock
B2: out of stock
Z9: not found
C3: 5 available for promotion

4 行目が表示されれば、if let によって Some(5) の内部にある値を取り出せたことが確認できます。また、値が存在しない場合の処理を新たに作っていないことも分かります。

まとめ

Some と None を match し、Some(0) と値の不在の違いを維持し、存在する場合だけ処理が必要なときに if let を使用しました。