map_err для варианта <T> - PullRequest
       85

map_err для варианта <T>

0 голосов
/ 01 августа 2020

Итак, Result<T, E> имеет довольно аккуратный метод map_err, который позволяет обрабатывать ошибки функциональным способом:

use std::io::Result;
use std::error::Error;
use std::string::ToString;
use std::io;

fn init() -> Result<u32> { Ok(42) }

fn do_work(_data: u32) -> Result<()> { Err(io::Error::new(io::ErrorKind::Other, "IO Error!")) }

fn handle_error<E: Error + ToString>(error: E, message: &str) -> E {
    eprintln!("{}: {}", message, error.to_string());
    error
}

fn main() {
    let _ = init()
        .map_err(|e| handle_error(e, "Init error"))
        .and_then(do_work)
        .map_err(|e| handle_error(e, "Work error")); // "Work error: IO error"
}

Было бы здорово иметь такой же функциональный стиль для обработки Option<T>::None:

use std::io::Result;
use std::error::Error;
use std::string::ToString;
use std::io;

fn init_opt() -> Option<u32> { Some(42) }

fn do_work_opt(_data: u32) -> Option<()> { None }

fn handle_none(message: &str) {
    eprintln!("{}", message);
}

fn main() {
    let _ = init_opt()
        .map_none(|| handle_none("Init error"))
        .and_then(do_work_opt)
        .map_none(|| handle_none("Work error")); // "Work error"
}

Но я не вижу подходящей замены для этого метода в документации Option

Это можно сделать с помощью подобного настраиваемого трейта

trait MapNone {
    fn map_none(self, op: impl FnOnce() -> ()) -> Self;
}

impl<T> MapNone for Option<T> {
    fn map_none(self, op: impl FnOnce() -> ()) -> Self {
        if self.is_none() { op(); } 
        self
    }
}

Но я уверен, что мне чего-то не хватает, и есть хороший способ сделать то же самое через стандартную библиотеку.

Full Playground

1 Ответ

2 голосов
/ 01 августа 2020

Существует функция Option::or_else, которая является обобщенной формой того, что вы предлагаете. Вместо того, чтобы требовать, чтобы функция возвращала тип устройства, он возвращает Option, поэтому просто попросите его вернуть None, чтобы смоделировать ваш вариант использования.

fn main() {
    let _ = init_opt()
        .or_else(|| {handle_none("Init error"); None})
        .and_then(do_work_opt)
        .or_else(|| {handle_none("Work error"); None}); // "Work error"
}

Конечно, теперь также можно вернуть Some(x) вместо этого ветвиться иначе.

Наконец, or_else и and_then также можно комбинировать с map_or_else:

fn main() {
    let _ = init_opt()
      .map_or_else(|| {handle_none("Init error"); None}, do_work_opt)
      .or_else(|| {handle_none("Work error"); None}); // "Work error"
}

Этот код должен делать то же, что и выше; map_or_else разветвляется в зависимости от того, будет ли опция None или содержать значение.

...