Источник: Nuances of Programming
Какой язык программирования вам подойдет? Пройдите тест SkillFactory из 7 вопросов и узнайте, какой язык программирования подходит лично вам.
Определение
Тип Result — это обобщенное перечисление из стандартной библиотеки Rust и результат вычисления: успешный с вариантом Ok и неуспешный с Err.
Тип Result определяется так:
enum Result<T, E> {
Ok(T),
Err(E),
}
The T и E — это типы успешных и неуспешных результатов соответственно. Result<T, E> применяется при вычислении, в котором возвращается значение типа T при успехе и ошибка типа E при неуспехе.
Пример использования
Вот Result для оборачивания результата функции, которая может выполниться неуспешно:
fn parse_int(s: &str) -> Result<i32, std::num::ParseIntError> {
s.parse::<i32>().map_err(|e| e.into())
}
fn main() {
let result = parse_int("5");
match result {
Ok(n) => println!("Parsed integer: {}", n),
Err(e) => println!("Error parsing integer: {}", e),
}
}
Если попытка спарсить число из строки успешна, в функции parse_int возвращается вариант Ok с полученным числом, если неуспешна — вариант Err с ParseIntError.
Обработка значений Result
Для обработки значения Result применяется сопоставление с образцом. В примере выше для обработки вариантов Ok и Err использовано выражение match.
Кроме того, чтобы извлечь значение из варианта Ok, применяется метод unwrap. Но он «запаникует», если значение — вариант Err:
let result = parse_int("5");
let n = result.unwrap(); // «запаникует», если результат — вариант Err
println!("Parsed integer: {}", n);
Из-за возможности паники unwrap() не рекомендуется использовать в производственном коде.
Другой способ обработки значения Result — методы map и map_err: значение внутри варианта Ok и Err преобразуется применением к нему функции.
В функции divide, например, делятся 2 числа и возвращается Result — показатель успешного деления:
fn divide(x: i32, y: i32) -> Result<i32, DivisionError> {
if y == 0 {
return Err(DivisionError::DivideByZero);
}
Ok(x / y)
}
map() и map_err()
Чтобы преобразовать успешный результат деления в другой тип, например в число с плавающей точкой, применяется метод map:
let result = divide(10, 2);
let f: Result<f32, DivisionError> = result.map(|n| n as f32);
Чтобы преобразовать значение ошибки в другой тип, применяется метод map_err:
let result = divide(10, 0);
let f: Result<i32, &str> = result.map_err(|e| match e {
DivisionError::DivideByZero => "Divide by zero",
DivisionError::Negative => "Cannot divide by negative number",
});
Другие методы обработки результата
Кроме методов map и map_err, у типа Result имеются и другие способы манипулирования значениями и их обработки:
fn add_one(x: i32) -> Result<i32, &'static str> {
if x > 100 {
return Err("Number too large");
}
Ok(x + 1)
}
fn add_two(x: i32) -> Result<i32, &'static str> {
if x > 50 {
return Err("Number too large");
}
Ok(x + 2)
}
fn add_three(x: i32) -> Result<i32, &'static str> {
if x > 30 {
return Err("Number too large");
}
Ok(x + 3)
}
// С использованием and_then
let result = add_one(5).and_then(|x| add_two(x)).and_then(|x| add_three(x));
assert_eq!(result, Ok(11));
// С использованием or_else
let result = add_one(105).or_else(|e| add_two(5)).or_else(|e| add_three(5));
assert_eq!(result, Ok(7));
// С использованием unwrap_or
let result = add_one(105).unwrap_or(100);
assert_eq!(result, 100);
// С использованием unwrap_or_else
let result = add_one(105).unwrap_or_else(|e| -1);
assert_eq!(result, -1);
В этих примерах показаны 3 функции, каждая из которых выполняет операцию и возвращает значение Result.
Методом and_then они связываются, и, если все вычисления успешные, возвращается конечный результат операций. Если какое-либо вычисление неуспешно, сразу возвращается вариант Err.
Метод or_else аналогичен, но применяется к варианту Err, а не Ok. Здесь также указывается резервное вычисление, если исходное неуспешно.
В методе unwrap_or возвращается значение внутри варианта Ok или значение по умолчанию, если Result — вариант Err. Метод пригодится, когда в случае ошибки нужно указать значение по умолчанию, а не обрабатывать ее явно.
Метод unwrap_or_else аналогичный, но, чтобы получить значение по умолчанию, в качестве аргумента принимается замыкание, применяемое к значению ошибки внутри варианта Err. Метод пригодится, когда нужно вычислить значение по умолчанию на основе значения ошибки.
Читайте также:
Перевод статьи Pascal Zwikirsch: Rust: Result Type Explained