Чистый способ преобразовать Result в Option в Rust
Основные способы преобразования Result в Option
Наиболее чистым и прямым способом является использование метода .ok() — это стандартный идиоматичный подход. Он отбрасывает ошибку, но это именно то, что нужно, когда вас интересует только успешный результат. Рассмотрим все варианты:
// 1. Самый простой — .ok()
let result: Result<i32, String> = Ok(42);
let option: Option<i32> = result.ok(); // Some(42)
let result_err: Result<i32, String> = Err("ошибка".to_string());
let option_err: Option<i32> = result_err.ok(); // None
// 2. Если нужно сохранить ошибку для логирования — map_err + ok
let option_with_log: Option<i32> = result_err
.map_err(|e| {
log::error!("Ошибка: {}", e);
e // возвращаем ту же ошибку
})
.ok();
// 3. Pattern matching для максимального контроля
let option_match: Option<i32> = match result {
Ok(val) => Some(val),
Err(e) => {
eprintln!("Ошибка: {}", e);
None
}
};
Преобразование через итераторы
Ещё один мощный приём — использование итераторов. Метод .into_iter() на Result возвращает итератор с 0 или 1 элементом:
let results = vec![Ok(1), Err("err"), Ok(3)];
// Собираем только успешные значения
let successes: Vec<i32> = results.into_iter()
.filter_map(Result::ok)
.collect(); // [1, 3]
// Или с сохранением ошибок
let (oks, errs): (Vec<_>, Vec<_>) = results.into_iter()
.partition(Result::is_ok);
Рекомендации по выбору метода
- Используйте
.ok()когда ошибка не важна — это самый короткий и читаемый способ. - Используйте
map_err + okесли нужно залогировать ошибку перед преобразованием. - Используйте
matchдля нетривиальной обработки (например, разные действия для разных типов ошибок). - Для коллекций применяйте
filter_mapилиpartition.
Полный пример с реальным сценарием
use std::env;
use std::num::ParseIntError;
fn get_port() -> Option<u16> {
let port_str = env::var("PORT")
.map_err(|e| {
eprintln!("Не удалось прочитать PORT: {}", e);
e
})
.ok()?;
let port: u16 = port_str.parse()
.map_err(|e: ParseIntError| {
eprintln!("Неверный формат порта: {}", e);
e
})
.ok()?;
Some(port)
}
fn main() {
match get_port() {
Some(port) => println!("Порт: {}", port),
None => println!("Порт не задан или неверный"),
}
}
В этом примере мы логируем ошибки, но в итоге получаем чистый Option. Оператор ? работает и с Option — если None, функция вернёт None.
Помните: в Rust нет "грязных" способов — все методы легитимны. Выбор зависит от того, что вы хотите сделать с ошибкой. Если её нужно игнорировать — .ok() это нормально. Если нужно обработать — используйте match или комбинаторы.
Альтернатива: использование трейта From и обобщённых функций
Можно написать свою небольшую утилиту, которая делает код более выразительным, особенно если такое преобразование встречается часто:
trait IntoOption<T> {
fn into_option(self) -> Option<T>;
}
impl<T, E: std::fmt::Display> IntoOption<T> for Result<T, E> {
fn into_option(self) -> Option<T> {
match self {
Ok(val) => Some(val),
Err(e) => {
eprintln!("Предупреждение: ошибка проигнорирована: {}", e);
None
}
}
}
}
// Использование
let result: Result<i32, String> = Err("тест".into());
let opt: Option<i32> = result.into_option(); // None + лог
Этот подход позволяет централизовать логику преобразования и добавить логирование по умолчанию. Однако помните, что в большинстве случаев встроенный .ok() более идиоматичен и понятен другим разработчикам. Используйте кастомные трейты только если у вас специфические требования к обработке ошибок во всём проекте.