Why do I prefer "Option :: ok_or_else" instead of "Option :: ok_or"?

I just saw the following change to the pull request:

- .ok_or(Error::new(ErrorKind::Other, "Decode error"));
+ .ok_or_else(|| Error::new(ErrorKind::Other, "Decode error"));

      

The only differences I know are:

  • In ok_or

    we have already created Error

    on Error::new

    and passed it to the adapter.
  • In ok_or_else

    we passed a closure that produces such a value, but it cannot be called if Option

    there is Some

    data in.

Did I miss something?

+3


source to share


3 answers


The main reason for using ok_or_else

or any of the methods ..._or_else

is to avoid executing the function when it is not needed. Whether Option::ok_or_else

or Option::unwrap_or_else

not there is a need to run additional code if Option

- Some

. This may make the code faster, depending on what happens in the event of an error.

This example Error::new

is probably doing the highlighting, but it could also write to standard, make a network request, or whatever any piece of Rust code can do; it's hard to tell from the outside. It is generally safer to place this kind of code in a closure, so you don't have to worry about extraneous side effects when success happens.



Clippy is for you too:

warning: use of `unwrap_or` followed by a function call
 --> src/main.rs:3:15
  |
3 |     let foo = foo.unwrap_or("hello".to_string());
  |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `foo.unwrap_or_else(|| "hello".to_string())`
  |
  = note: #[warn(or_fun_call)] on by default
  = help: for further information visit https://github.com/Manishearth/rust-clippy/wiki#or_fun_call

      

+7


source


Signature std::io::Error::new

equals

fn new<E>(kind: ErrorKind, error: E) -> Error 
where
    E: Into<Box<Error + Send + Sync>>, 

      



This means it Error::new(ErrorKind::Other, "Decode error")

is allocating memory on the heap because it error

needs to be converted to Box<Error + Send + Sync>

for use.

Therefore, this pull request removes unnecessary memory allocation / deallocation when value Result

is Result::Ok

.

+2


source


In addition to performance implications, more complex arguments ok_or

can produce unexpected results if you are not careful enough; consider the following case:

fn main() {
    let value: Option<usize> = Some(1);

    let result = value.ok_or({ println!("value is not Some!"); 0 }); // actually, it is

    assert_eq!(result, Ok(1)); // this holds, but "value is not Some!" was printed
}

      

This could have been avoided with ok_or_else

(and the same goes for other functions *_or_else

) since the closure is not evaluated if the option Some

.

+1


source







All Articles