Тип Result в Rust

Определение

Тип 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. Метод пригодится, когда нужно вычислить значение по умолчанию на основе значения ошибки.

Читайте также:

Читайте нас в TelegramVK и Дзен


Перевод статьи Pascal Zwikirsch: Rust: Result Type Explained

Предыдущая статья3 основных принципа несвязных приложений
Следующая статьяПодключение приложений Android к серверу с помощью gRPC