Reputation: 7927
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.
Upvotes: 5
Views: 4759
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