2021-12-21

vak: (Default)
Rust на первый взгляд не особо отличается от Си++. Тоже классы и методы, ничего особенного. Однако подход ко многим вещам отличается. К примеру, обработка особых ситуаций и ошибок делается совсем по другому. Здесь я составил короткий обзор со ссылками на документацию.

Тип Option

Что, если значение может отсутствать? К примеру, функция может вернуть значение, если оно вычислено успешно, и вернуть что-то другое, если не получилось. Для таких случаев существует тип Option. Это энум с двумя вариантами:
pub enum Option<T> {
None,
Some(T),
}
Если значение val получилось вычислить, возвращаем Some(val). Иначе возвращаем None. Пример такой функции - сложение двух чисел с обнаружением переполнения:
let x = 123.checked_add(567);
Здесь x получит значение Some(690), поскольку переполнения не было.

Тип Result

Часто при возникновении особых ситуаций нужно вернуть дополнительную информацию, уточняющую причину ошибки. Просто None недостаточно. Для этого существует другой тип: Result. Это тоже энум с двумя вариантами:
enum Result<T, E> {
Ok(T),
Err(E),
}
В отличие от Option, в случае особой ситуации вместо None используется вариант Err(x), где значение x содержит код ошибки. Большинство функций стандартной библиотеки возвращает Result.

Тип Error

В энуме Result<T,E> тип E может быть каким угодно, но в стандартной библиотеке принято давать ему интерфейс (трейт) std::error::Error. У трейта Error есть несколько малополезных методов низкого уровня, но главное - его можно напечатать через print!(), и получить объяснение ошибки.

Оператор match

Результат типа Option или Result обычно обрабатывается с помощью оператора match. Пример для Option:
let val = match 123.checked_add(567) {
Some(x) => x,
None => return None,
};
Пример для Result:
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) произойдёт преждевременный возврат из текущей функции.

Предыдущие примеры сокращаются до:
let val = 123.checked_add(567)?;

let file = File::open("hello.txt")?;
Заметьте, что чтобы вопросительный знак правильно работал, текущая функция должна быть объявлена как возвращающая значение Option<T> или Result<T, E> соответственно.

Выделение значений

Для типовой обработки значений типа 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<T, E>, обычно требуется выделить значение из Ok(value), а если попадётся Err(e) - завершиться с ошибкой или выдать альтернативное значение.
  • 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 надо выделить значение из варианта Err(e), а если попадётся Ok(v) - завершиться с ошибкой или выдать альтернативное значение.
  • 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 в Result:
  • option.ok_or(err) - преобразовать Some(v) в Ok(v), а None в Err(err)
  • option.ok_or_else(func) - преобразовать Some(v) to Ok(v), а None в Err(func())
Пример:
let x = 123.checked_add(567).unwrap();
Здесь x получит значение 590. Если бы случилось переполнение, программа завершилась бы с сообщением:
called `Option::unwrap()` on a `None` value
vak: (Default)
Интервью с Михаилом Донским, одним из создателей шахматной программы "Каисса".