Is there a way to relate the display and deployment of results?

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 try to make it less like a festive holiday tree and thought of a chain. Please note that each step in this chain creates a different Result , so this obviously will not 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 types of errors are different for each step, which means that I cannot just replace .map with .and_then .

I think I'm looking for something similar to JavaScript promises. That is, the promise returned from within the promise reveals the inner promise. The signature should apparently be as follows:

 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 any other way to get rid of my holiday village village?

+6
source share
2 answers

Is there such a mechanism in Rust?

Yes - although not all in one shot, as you have already presented. Let's look at 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 will not work - suppose we start with the Err variant - how does this code know how to convert from E to D ? Also, &self not suitable for functions that want to convert types; usually take self .

There are two components you 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 need a type that can represent both types of errors. I will be lazy and use Box<Error>

When combined with each other, 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 generic type, and the result is bound using and_then . Presumably your real code will generate an error type appropriate for your problem, and then you will use this in the map_err call. I would implement From , then you can simply:

 let conf: Result<_, Box<Error>> = env::var("CONF") .map_err(Into::into) .and_then(|f| File::open(f).map_err(Into::into)); 
+7
source

If you really want to ignore the results you do 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 was created from the answer I asked on a similar question in Option s , but it works with any if let . Although with Result you often want to use Err in some way, so I usually lean towards the approach explained by Shepmaster or ? / try! .

+4
source

Source: https://habr.com/ru/post/1013134/


All Articles