Обработка ошибок в Rust
2021-12-21 19:04![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
Rust на первый взгляд не особо отличается от Си++. Тоже классы и методы, ничего особенного. Однако подход ко многим вещам отличается. К примеру, обработка особых ситуаций и ошибок делается совсем по другому. Здесь я составил короткий обзор со ссылками на документацию.
Предыдущие примеры сокращаются до:
Для Option<T>, обычно нужно выделить значение из Some(value), а если встретится None - завершиться с ошибкой или выдать альтернативное значение.
Тип Option
Что, если значение может отсутствать? К примеру, функция может вернуть значение, если оно вычислено успешно, и вернуть что-то другое, если не получилось. Для таких случаев существует тип Option. Это энум с двумя вариантами:Если значение val получилось вычислить, возвращаем Some(val). Иначе возвращаем None. Пример такой функции - сложение двух чисел с обнаружением переполнения:pub enum Option<T> {
None,
Some(T),
}
Здесь x получит значение Some(690), поскольку переполнения не было.let x = 123.checked_add(567);
Тип Result
Часто при возникновении особых ситуаций нужно вернуть дополнительную информацию, уточняющую причину ошибки. Просто None недостаточно. Для этого существует другой тип: Result. Это тоже энум с двумя вариантами:В отличие от Option, в случае особой ситуации вместо None используется вариант Err(x), где значение x содержит код ошибки. Большинство функций стандартной библиотеки возвращает Result.enum Result<T, E> {
Ok(T),
Err(E),
}
Тип Error
В энуме Result<T,E> тип E может быть каким угодно, но в стандартной библиотеке принято давать ему интерфейс (трейт) std::error::Error. У трейта Error есть несколько малополезных методов низкого уровня, но главное - его можно напечатать через print!(), и получить объяснение ошибки.Оператор match
Результат типа Option или Result обычно обрабатывается с помощью оператора match. Пример для Option:Пример для Result:let val = match 123.checked_add(567) {
Some(x) => x,
None => return None,
};
let result = File::open("hello.txt");
let file = match result {
Ok(x) => x,
Err(e) => return Err(e),
};
Оператор '?'
Вышеприведённые фрагменты обработки значений типа Result или Option настолько часто встречаются, что для удобства был придуман способ их упростить. Если после выражения поставить вопросительный знак, значение будет обработано посредством match, ровно как написано выше. То есть в случае None (для Option) или Err (для Result) произойдёт преждевременный возврат из текущей функции.Предыдущие примеры сокращаются до:
Заметьте, что чтобы вопросительный знак правильно работал, текущая функция должна быть объявлена как возвращающая значение Option<T> или Result<T, E> соответственно.let val = 123.checked_add(567)?;
let file = File::open("hello.txt")?;
Выделение значений
Для типовой обработки значений типа Option или Result есть набор полезных методов.Для Option<T>, обычно нужно выделить значение из Some(value), а если встретится None - завершиться с ошибкой или выдать альтернативное значение.
- option.unwrap() - если None, вызвать panic!()
- option.expect(msg) - если None, вызвать panic!() с указанным сообщением об ошибке
- option.unwrap_or(val) - если None, выдать указанное значение
- option.unwrap_or_default() - если None, выдать дефолтное значение для типа T
- option.unwrap_or_else(func) - если None, вызвать функцию и вернуть её значение
- result.unwrap() - если Err, вызвать panic!()
- result.expect(msg) - если Err, вызвать panic!() с указанным сообщением об ошибке
- result.unwrap_or(val) - если Err, выдать указанное значение
- result.unwrap_or_default() - если Err, выдать дефолтное значение для типа T
- result.unwrap_or_else(func) - если Err, вызвать функцию и вернуть её значение
- result.unwrap_err() - если Ok, вызвать panic!()
- result.expect_err(msg) - если Ok, вызвать panic!() с указанным сообщением об ошибке
Преобразование Result ⟷ Option
Можно преобразовать значение типа Result в Option:- result.ok() - преобразовать Ok(v) в Some(v), а Err(e) в None
- result.err() - преобразовать Err(e) to Some(e), а Ok(v) в None
- option.ok_or(err) - преобразовать Some(v) в Ok(v), а None в Err(err)
- option.ok_or_else(func) - преобразовать Some(v) to Ok(v), а None в Err(func())
Здесь x получит значение 590. Если бы случилось переполнение, программа завершилась бы с сообщением:let x = 123.checked_add(567).unwrap();
called `Option::unwrap()` on a `None` value
no subject
Date: 2021-12-22 03:40 (UTC)Тип Result у них не аппликативный, это, конечно, немножко дефект. Он вообще монада? Я что-то уже не уверен.
Но нужен аппликативный. Я что и наслаждался моим таким же типом, что можно много что нафигачить.
А вообще, в Скале все это делается изощреннее, конечно. Опцию в список только так.
no subject
Date: 2021-12-22 04:36 (UTC)no subject
Date: 2021-12-22 05:44 (UTC)no subject
Date: 2021-12-22 14:16 (UTC)Мне кажется, не особо и надо абстрагировать и писать, мол, что оно монада. Просто по факту. map, flatmap, ap, unit.
no subject
Date: 2021-12-22 14:21 (UTC)Это детали имплементации; если к ним добавить
то и будет все что надо. Практически. Sorry, два года уже не трогал раст, забыл синтаксис.
no subject
Date: 2021-12-22 05:40 (UTC)no subject
Date: 2021-12-22 07:32 (UTC)no subject
Date: 2022-01-08 05:49 (UTC)Функциональное программирование
Date: 2022-01-12 06:10 (UTC)no subject
Date: 2021-12-22 09:11 (UTC)Впрочем, сейчас вся индустрия именно этим и занимается.
no subject
Date: 2021-12-22 11:25 (UTC)Conditions (как в CL), когда обработчику наверху предоставляются Restarting points на выбор: как возобновить с того места где ошибка (возврат обратно вниз по стеку)
no subject
Date: 2021-12-22 14:18 (UTC)Ой, ничо се. Это у нас когда-то в древние времена на фортране было имплементировано. Типа после того, как электричество починили и мы включили комп, мы вернулись в точку, где были, и решаем, куда дальше.
no subject
Date: 2022-01-08 05:46 (UTC)no subject
Date: 2021-12-22 14:10 (UTC)Второй тип не исключает первый. И не так уж часто надо бросать исключение при первой же ошибке - если мы парсим документ, то лучше собрать все возможные проблемы (вот тут-то аппликативный функтор и нужен).
Насчет же упрятывания под ковер - это типичное программирование на джаве. Имя доктора не нашли? Значит, его имя - пустая строка. Или null. Это все тоже имеет математический аналог. Категория Эйленберга-Мура. Где на всякую хрень есть еще "дефолтное значение". What is the default value for a birthday? Christ circumcision date.
no subject
Date: 2021-12-22 14:16 (UTC)"возврат наверх описания ошибки с отладочной информацией," может быть как исключением, обрывающим ветку выполнения, так и просто контейнером, который поднимается наверх вплоть до конечного пользователя и представляет в табличном виде что-то вроде "пациенты без имени домашнего врача" и "пациенты с неизвестной датой рождения". После чего пользователь или дополняет данные, или решает перевести ошибку в разряд корректых неполных записей.
no subject
Date: 2021-12-22 08:09 (UTC)Все таки, в этом смысле, питон на голову выше этих языков
no subject
Date: 2021-12-22 08:15 (UTC)no subject
Date: 2021-12-24 04:45 (UTC)no subject
Date: 2021-12-24 05:08 (UTC)no subject
Date: 2021-12-22 14:13 (UTC)Скала тоже хороша в смысле скобочек. Но давайте не будем путать язык с линейной логикой и языки с интуиционистской. Разные вещи, концептуально. В линейной логике вещи не возникают из ничего и не исчезают в никуда.
А уж полутиповые языки просто не совсем являются языками. Где их математическая модель? И когда будет обнаружена ошибка в коде, в продакшене?
no subject
Date: 2021-12-22 16:40 (UTC)no subject
Date: 2021-12-22 10:40 (UTC)std::variant
std::any
std::optional
std::experimental::expected
no subject
Date: 2021-12-22 13:39 (UTC)no subject
Date: 2021-12-22 14:11 (UTC)В плюсах уже монады есть? Строуструп как-то не сознавался.
no subject
Date: 2021-12-22 21:05 (UTC)no subject
Date: 2021-12-23 22:04 (UTC)no subject
Date: 2021-12-23 22:16 (UTC)