Error Handling
Внешние ссылки:
- https://youtu.be/f82wn-1DPas
- https://blog.logrocket.com/error-handling-rust/
- https://rust.nizeclub.ru/_11.html
Option
Option — это перечисление (enum), которое используется, когда значение может быть либо “чем-то” (Some), либо “ничем” (None). Это замена привычным null или nil из других языков, но с важным отличием: в Rust вы обязаны явно обработать возможность отсутствия значения. Определение Option в стандартной библиотеке:
где:
T— это любой тип данных (например,i32,Stringи т.д.).Some(T)— значение есть, и оно равноT.None— значения нет.
Result
Result — это перечисление, которое используется для операций, которые могут завершиться успехом (Ok) или неудачей (Err):
где:
T— тип возвращаемого значения при успехе.E— тип ошибки при неудаче.
Обработка Option и Result
- Использовать
matchи обработать все варианты поведения; unwrap— “дай мне значение или паника” (еслиNoneилиErr, программа крашится);unwrap_or— “дай мне значение или что-то другое” (еслиNoneилиErr, возвращает запасное значение, которое ты указал);expect— “дай мне значение или паника с моим текстом” (какunwrap, но с твоим сообщением при краше);is_some— “скажи, есть ли там значение?” (возвращаетtrue, еслиSomeвOption, иfalse, еслиNone);is_none— “скажи, пусто ли там?” (возвращаетtrue, еслиNoneвOption, иfalse, еслиSome). Если коротко:unwrap,unwrap_orиexpectпытаются достать значение и что-то с ним сделать, аis_someиis_noneпросто проверяют, что внутри, не трогая само значение.
Обработка ошибки с map_err()
.map_err() — это метод, который позволяет преобразовать тип ошибки, не трогая успешное значение.
Работа map_err() с цепочками операций:
В широком использовании удобнее использовать context из библиотеки anyhow. Однако, map_err нужен для преобразования в другой тип ошибки:
Ошибки, не реализующие std::error::Error:
Кастомная логика обработки ошибки:
В коде, использующем anyhow, лучше context() и with_context() перед map_err(|e| anyhow!(...)). А map_err() для случаев, когда нужно преобразовать ошибку в другой тип или реализовать нестандартную логику обработки.
Оператор ‘?’
Оператор ? используется в функциях, возвращающих Result. Он автоматически возвращает Err из функции, если результат — Err, или извлекает значение из Ok:
Ошибки без восстановления
Ряд ошибок приводит к вылету приложения. Также можно вручную вызвать вылет командой panic!:
При этом некоторое время тратится на закрытие приложения, очистку стека и данных. Можно переложить это на ОС, введя настройку в Cargo.toml:
Пользовательские ошибки с enum
Иногда стандартных типов ошибок (например, String) недостаточно. Вы можете создать свои собственные ошибки с помощью enum:
Преимущества:
- Чёткое определение всех возможных ошибок.
- Легко расширять (добавьте новый вариант в
enum).
Стратегии работы с ошибками
Подготовка примера
Допустим, мы берём вектор из строк-чисел, складываем их и возвращаем сумму как строку:
Для конвертации строки в числа, нужно реализовать функцию to_int в соответствии со стратегиями обработки ошибочного ввода. Конвертацию мы делаем функцией parse(), которая возвращает тип Result<T,E>, где T - значение, E - код ошибки.
Стратегия 1 - паника
В случае неверного ввода, программа полностью останавливается в панике. Метод unwrap() у типа Result<T,E> убирает проверки на ошибки и есть договор с компилятором о том, что ошибки в этом месте быть не может. Если она есть, программа падает с паникой:
Стратегия 2 - паника с указанием причины
В случае неверного ввода, программа сообщает фразу, заданную автором, далее полностью останавливается в панике. Метод expect() аналогичен unwrap(), но выводит сообщение:
Стратегия 3 - обработать то, что возможно обработать
Можно сконвертировать и прибавить к результату те строки, которые позволяют это сделать, проигнорировать остальные. Метод unwrap_or() позволяет указать возвращаемое значение в случае ошибки:
Более предпочтительный вариант использовать закрытие unwrap_or_else(), так как метод unwrap_or() будет вызван ДО того как будет отработана основная команда, ВНЕ ЗАВИСИМОСТИ от того, будет ли её результат Some или None. Это потеря производительности, а также потенциальные глюки при использовании внутри unwrap_or() сложных выражений. Закрытие unwrap_or_else() будет вызвано только в случае None, иначе же эта ветка не обрабатывается:
Стратегия 4 - решение принимает вызывающая функция
Вместо возврата числа, возвращаем тип Option<число> - в зависимости от успешности функции, в нём будет либо само число, либо None:
И тогда вызывающая функция должна обработать результат:
Более короткий вариант записи через if let:
Тип Option<T> также имеет метод unwrap_or(), отсюда ещё вариант записи:
Стратегия 5 - в случае проблем, передать всё в основную программу
Вместо передачи значения из функции, в случае каких-либо проблем, мы возвращаем None:
Стратегия 6 - передать всё в основную программу с объяснением ошибки
Мы возвращаем проблему в основную программу с объясением проблемы. Для этого заводим структуру под ошибку, и передаём уже не объект Option<T>, а объект Result<T,E>, где E = SummationError. Для такого объекта есть метод ok_or(), который либо передаёт значение, либо передаёт ошибку нужного типа:
Вместо выдумывать свой собственный тип и конвертировать вывод метода parse() из Result<T,E> в Option<T>, а потом обратно, можно сразу протащить ошибку в объекте Result<T,E> в главную программу:
Однако, мы хотим скрыть подробности работы и ошибки от главной программы и передать ей ошибку в понятном виде, без разъяснения деталей её возникновения. Для этого можно сделать трансляцию ошибки из библиотечной в собственный тип, и далее передать методом map_err():
Где можно использовать оператор ‘?’
Оператор ? можно использовать только в функциях для возврата совместимых значений типа Result<T,E>, Option<T> или иных данных со свойством FromResidual. Для работы такого возврата в заголовке функции должен быть прописан возврат нужного типа данных.
При использовании ? на выражении типа Result<T,E> или Option<T>, ошибка Err(e) или None будет возвращена рано из функции, а в случае успеха - выражение вернёт результат, и функция продолжит работу.
Пример функции, которая возвращает последний символ 1ой строки текста:
Дополнительные библиотеки работы с ошибками
- Anyhow - статья
- thiserror
- color-eyre