Alexey S. Larionov
Alexey S. Larionov

Reputation: 7927

map_err for Option<T>

So Result<T, E> has a pretty neat method map_err that allows to handle errors in a functional way:

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"
}

It would be cool to have the same functional style for handling 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"
}

But I don't see any suitable replacement for this method in documentation of Option

It can be done with a custom trait like that

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
    }
}

But I'm sure I'm missing something and there is some pretty way of doing the same via standard library.

Full Playground

Upvotes: 5

Views: 4759

Answers (1)

Zorf
Zorf

Reputation: 6464

The function Option::or_else exists that is a generalized form of what you propose. Rather than requiring it's function to return the unit type, it returns an Option, so simply have it return None to simulate your use case.

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"
}

One can of course also now return Some(x) instead to branch differently.

Finally, or_else and and_then can also be combined with 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"
}

This code should do the same as the above; map_or_else branches depending on whether the option be None or contain a value.

Upvotes: 6

Related Questions