I have this code.
if let Ok(file) = env::var("CONF") {
if let Ok(mut reader) = fs::File::open(&file) {
if let Ok(conf) = Json::from_reader(&mut reader) {
// do something with conf
}
}
}
I'm trying to make it less like a festive holiday tree and was thinking about chaining. Notice that each step in this chain produces another Result
, so clearly this won't work (we get Result
in Result
).
let conf = env::var("CONF")
.map(fs::File::open)
.map(Json::from_reader);
// do something with conf
Also my error types differ for each step, which means I can't just replace .map
with .and_then
.
I think I'm looking for something that is similar to JavaScript's promises. That is, a promise returned from inside a promise unwraps the inner promise. The signature should probably be along the lines of:
impl<T, E> Result<T, E> {
fn map_unwrap<F, U, D>(&self, op: F) -> Result<U, D>
where F: FnOnce(T) -> Result<U, D>
}
Is there such a mechanism in Rust? Is there another way to get rid of my festive holiday tree?
Is there such a mechanism in Rust?
Yes — although not all in one shot like you've presented. Let's review your theoretical signature:
impl<T, E> Result<T, E> {
fn map_unwrap<F, U, D>(&self, op: F) -> Result<U, D>
where
F: FnOnce(T) -> Result<U, D>,
{}
}
This cannot work - assume that we start with an Err
variant - how would this code know how to convert from E
to D
? Additionally, &self
isn't appropriate for functions that want to convert types; those usually take self
.
There are two components that you will need to combine:
Result::and_then
impl<T, E> Result<T, E> {
fn and_then<U, F>(self, op: F) -> Result<U, E>
where
F: FnOnce(T) -> Result<U, E>,
{}
}
Result::map_err
impl<T, E> Result<T, E> {
fn map_err<F, O>(self, op: O) -> Result<T, F>
where
O: FnOnce(E) -> F,
{}
}
Then you will need a type that can represent both error types. I'll be lazy and use Box<Error>
Combined together, you need something like:
use std::env;
use std::fs::File;
use std::error::Error;
fn main() {
let conf = env::var("CONF")
.map_err(|e| Box::new(e) as Box<Error>)
.and_then(|f| File::open(f).map_err(|e| Box::new(e) as Box<Error>));
}
Now each call converts the error value to a shared type, and the result is chainable with and_then
. Presumably, your real code would create an error type that is suited to your problem, and then you would use that in the map_err
call. I'd implement From
, then you can have just:
let conf: Result<_, Box<Error>> = env::var("CONF")
.map_err(Into::into)
.and_then(|f| File::open(f).map_err(Into::into));
If you actually want to ignore the results as you are doing with if let
you can use a macro like this:
macro_rules! iflet {
([$p:pat = $e:expr] $($rest:tt)*) => {
if let $p = $e {
iflet!($($rest)*);
}
};
($b:block) => {
$b
};
}
fn main() {
iflet!([Ok(file) = env::var("CONF")]
[Ok(mut reader) = File::open(&file)]
[Ok(conf) = Json::from_reader(&mut reader)] {
// do something with conf
});
}
Playground (without Json part)
The macro is originally from an answer I made to a similar question on Option
s, but it works with any if let
. Though, with Result
you often want to use the Err
in some way, so I would usually lean towards the approach explained by Shepmaster or ?
/try!
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With