Shanraq.org Shanraq.org
Знак вопроса: передать ошибку вызывающему коду
IT

Rust: с нуля до своего органайзера Урок 33 из 60

Знак вопроса: передать ошибку вызывающему коду

Урок 33. Сначала разобрать число минут, затем проверить его смысл и вернуть понятную ошибку.

Текст переведён с помощью ИИ.

Предварительно: уроки 12, 14 и 30–32. Если Ok и Err ещё смешиваются, повторите урок 32.

Образ и опорная карта

При заполнении заявки сотрудник сначала проверяет, написано ли число, затем — допустимо ли оно. Если первая проверка не прошла, нет смысла продолжать: заявка с объяснением возвращается отправителю. Знак ? после выражения типа Result делает похожий шаг: при Ok достаёт значение и продолжает функцию, при Err сразу возвращает ошибку из текущей функции. Это называют передачей ошибки вызывающему коду. Образ ограничен: ? не придумывает сообщение и не исправляет ошибку; оно передаёт её по правилам типов Rust.

Разбор → Ok продолжается / Err возвращается → проверка нуля → Ok или новая Err. Функция с ? здесь возвращает Result<u32, String>. Выражение перед ? тоже даёт Result<u32, String>, поэтому тип ошибки совпадает и не требуется преобразование. return Err(...) — знакомый ранний возврат из урока 14: функция заканчивается сразу. В отличие от ?, он создаёт новое объяснение для нашего правила о нуле.

Разбор и проверка по отдельности

Сохраните прежний src/main.rs проекта organizer, замените файл целиком и выполните cargo run рядом с Cargo.toml. Других файлов и зависимостей нет. Предскажите три строки.

parse_number отличает текст числа от неподходящего текста. checked_minutes сначала получает число через parse_number(text)?. При ошибке следующие строки этой функции не выполняются. Если число ноль, создаётся другая ошибка. main решает, что показать пользователю; функции ниже не печатают сообщения сами.

fn parse_number(text: &str) -> Result<u32, String> {
    match text.parse::<u32>() {
        Ok(number) => Ok(number),
        Err(_) => Err(String::from("Нужно целое число")),
    }
}

fn checked_minutes(text: &str) -> Result<u32, String> {
    let minutes = parse_number(text)?;
    if minutes == 0 {
        return Err(String::from("Минуты должны быть больше нуля"));
    }
    Ok(minutes)
}

fn main() {
    for text in ["20", "0", "скоро"] {
        match checked_minutes(text) {
            Ok(minutes) => println!("Принято: {minutes} мин"),
            Err(message) => println!("Ошибка: {message}"),
        }
    }
}
Принято: 20 мин
Ошибка: Минуты должны быть больше нуля
Ошибка: Нужно целое число

Итак, ошибка синтаксиса и ошибка смысла не смешиваются. ? не делает автоматический вывод в терминал: текст появится только в main, где Err явно обрабатывается. Это позволит позднее отделить логику органайзера от его командной оболочки.

Где ? допустим

? можно применять там, где текущая функция умеет вернуть совместимую ошибку. Обычный fn main() без указанного типа результата возвращает () и не может передать наружу Err(String). Этот отдельный пример намеренно отклоняется с E0277:

fn main() {
    let value: Result<u32, String> = Ok(20);
    let minutes = value?;
    println!("{minutes}");
}

У ? есть и другие случаи применения, но пока держимся двух функций с совместимым Result<u32, String>. Если типы ошибок различаются, нужна явно изученная возможность преобразования; не вставляйте случайное .unwrap() ради исчезновения ошибки компиляции.

Проверка понимания

  1. Что происходит при Ok(20) перед ??
  2. Выполнится ли проверка нуля после Err из parse_number?
  3. Кто печатает сообщение пользователю?

Проверьте: получаем 20 и идём дальше; нет, ошибка возвращается раньше; main. Восстановите опорную карту без страницы.

Задание

Обязательное. Сделайте parse_number и checked_minutes с ?, как в примере, но используйте сообщения Введите число и Минуты не могут быть нулём. Проверьте "15", "0" и "много". Ожидайте Принято: 15 мин и два разных сообщения об ошибке. Не печатайте внутри функций проверки.

Граница. Объясните, почему текст "0" доходит до проверки нуля, а "много" — нет.

Подсказка

Строка let minutes = parse_number(text)?; находится в функции, возвращающей Result<u32, String>. Затем if minutes == 0 возвращает новый Err.

Эталон после попытки

fn parse_number(text: &str) -> Result<u32, String> {
    match text.parse::<u32>() {
        Ok(number) => Ok(number),
        Err(_) => Err(String::from("Введите число")),
    }
}

fn checked_minutes(text: &str) -> Result<u32, String> {
    let minutes = parse_number(text)?;
    if minutes == 0 {
        return Err(String::from("Минуты не могут быть нулём"));
    }
    Ok(minutes)
}

fn main() {
    for text in ["15", "0", "много"] {
        match checked_minutes(text) {
            Ok(minutes) => println!("Принято: {minutes} мин"),
            Err(message) => println!("Ошибка: {message}"),
        }
    }
}
Принято: 15 мин
Ошибка: Минуты не могут быть нулём
Ошибка: Введите число

Две функции отвечают за проверки, а main — за сообщение. Официальный разбор ?.

Предыдущий урок · Оглавление · Следующий урок

Если вы нашли ошибку или опечатку в тексте статьи, то сообщите нам об этом

Комментарии (0)

Пока нет комментариев. Будьте первым.