Обработка отсутствующих значений с помощью Option

RustBeginner
Практиковаться сейчас

Введение

При реальном поиске значение находится не всегда. Rust представляет такую возможность с помощью Option<T>: Some(value) означает, что значение существует, а None — что его нет. Благодаря этому отсутствие становится частью типа, а не скрывается за вымышленным числом и не приводит к панике.

Вы завершите небольшой отчёт об инвентаре. Он будет обрабатывать найденные и отсутствующие записи о запасах, различать существующую запись с нулевым количеством и отсутствие записи, а также использовать if let, когда действие требуется только для существующего значения.

Сопоставление существующих и отсутствующих значений

На этом шаге вы замените заготовленный отчёт исчерпывающим сопоставлением результата поиска, который может содержать значение или быть пустым.

Перейдите в подготовленный проект Cargo:

cd /home/labex/project/option-inventory

Откройте исходный файл:

nano src/main.rs

Подготовленная функция find_stock возвращает Option<u32>. Пока воспринимайте угловые скобки как обозначение «Option, который может содержать u32»; создание повторно используемых типов с параметрами в угловых скобках будет рассмотрено позже. Some(12) содержит количество товара на складе, а None вообще не содержит количества.

В нижней части подготовленной функции поиска _ => None использует шаблон-джокер _. Он означает «любой код, который не был сопоставлен предыдущей ветвью». Джокер не создаёт доступную переменную: он намеренно игнорирует несопоставленное значение и возвращает None.

Найдите заготовленный вызов println! внутри report и замените эту строку следующим сопоставлением:

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

Some(count) одновременно распознаёт существующее значение и присваивает его внутреннему числу локальное имя count. Ветка None явно обрабатывает отсутствие, поэтому сопоставление охватывает все возможные значения 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, а первые две — для Some, включая значение Some(0).

Различение нулевого количества и отсутствия записи

На этом шаге вы зададите отдельное поведение для существующей записи с нулевым количеством, вместо того чтобы считать ноль отсутствием записи.

Some(0) и None означают разные вещи. Some(0) сообщает, что поиск нашёл запись с количеством, равным нулю. None сообщает, что записи с таким кодом нет. Поэтому подставлять, например, 0 вместо отсутствующего значения небезопасно.

Снова откройте исходный файл:

nano src/main.rs

В сопоставлении внутри report добавьте следующую ветку непосредственно перед существующей веткой Some(count):

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

Ветки сопоставления проверяются сверху вниз. Более конкретный шаблон Some(0) должен находиться перед Some(count), поскольку общий шаблон также соответствует нулю.

Сохраните файл и выйдите из редактора, затем запустите программу:

cargo run --quiet

Теперь вывод должен быть таким:

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

Каждая строка теперь обозначает отдельное состояние: товар доступен, запись существует, но товар закончился, или запись отсутствует.

Выполнение действия только при наличии значения

На этом шаге вы используете if let для задачи, в которой действие требуется только при успешном поиске.

Исчерпывающее match удобно, когда для каждого случая требуется отдельное поведение. if let PATTERN = VALUE — это краткая условная конструкция с шаблоном для другой распространённой ситуации: выполнить один блок, если значение соответствует шаблону, и ничего не делать в противном случае.

Откройте исходный файл:

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 доступна внутри фигурных скобок. Блок else не нужен, потому что эта вспомогательная функция намеренно ничего не выводит для None.

В 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

Четвёртая строка подтверждает, что if let извлекла значение внутри Some(5), не создавая поведения для отсутствующего значения.

Итоги

Вы сопоставили Some и None, сохранили различие между Some(0) и отсутствием записи, а также использовали if let, когда действие требовалось только для существующего значения.