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() ради исчезновения ошибки компиляции.
Проверка понимания
- Что происходит при
Ok(20)перед?? - Выполнится ли проверка нуля после
Errизparse_number? - Кто печатает сообщение пользователю?
Проверьте: получаем 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)
Войдите, чтобы оставить комментарий →
Пока нет комментариев. Будьте первым.