bright-star
bright-star

Reputation: 6447

Why is .or_else() formulated to be called twice?

The Result docs give the following explanation for the .or_else() method:

fn or_else<F, O: FnOnce(E) -> Result<T, F>>(self, op: O) -> Result<T, F>

Calls op if the result is Err, otherwise returns the Ok value of self.

This function can be used for control flow based on result values.

Examples

fn sq(x: u32) -> Result<u32, u32> { Ok(x * x) }

fn err(x: u32) -> Result<u32, u32> { Err(x) }

assert_eq!(Ok(2).or_else(sq).or_else(sq), Ok(2));

assert_eq!(Ok(2).or_else(err).or_else(sq), Ok(2));

assert_eq!(Err(3).or_else(sq).or_else(err), Ok(9));

assert_eq!(Err(3).or_else(err).or_else(err), Err(3));

I think can parse the or_else type annotation with more whitespace:

fn or_else<F,                           // F being the return type of the Result?
           O: FnOnce(E) -> Result<T, F> // the function to run instead if error
          > 
           (self, op: O)                // arguments to the `.or_else()` method
-> Result<T, F>                         // return type is again Result

Assuming I've got that right, does that mean .or_else() simply gives you a Result with the Error replaced with the return value of the op function?

I can understand returning a Result yet again, since all code downstream of possibly error-throwing code is "stained" with the possibility of further errors (and it's up to the caller to handle that). But why the doubled calls in the examples? There are a couple different permutations, but I'm not sure what they're trying to show (or if that doubled or_else() pattern is idiomatic).

Upvotes: 0

Views: 221

Answers (1)

viraptor
viraptor

Reputation: 34145

The example may be a bit unfortunate, because it tries to show at the same time how does or_else work, and why would you use it.

To split it into two parts. First what does or_else actually do. If you get call it on Ok value, it passes the Ok value. If you call it on Err value, it executes the function. This example should be enough:

Ok(2).or_else(sq), Ok(2) // not called
Ok(2).or_else(err), Ok(2) // not called
Err(2).or_else(sq), Ok(4) // called, succeeds
Err(2).or_else(err), Err(3) // called, fails

Now, the why would you use it part. Imagine you're doing some operation which has many alternative approaches. For example you're trying to install some package on linux, but don't care which package manager is available - you're just going to brute-force it. With all the functions returning Result<...> you could do this:

install_with_apt().
    or_else(install_with_yum).
    or_else(install_with_pacman).
    or_else(install_with_dnf).
    or_else...

You'll know that if you got back Ok, at least one of those succeeded and if you get back Err all of them failed. The doubled .or_else() usage in the example if likely just trying to show you can easily chain this call.

Upvotes: 8

Related Questions