Why should I prefer `Option::ok_or_else` instead of `Option::ok_or`?

前端 未结 3 878
甜味超标
甜味超标 2020-12-03 11:31

I just saw the following change in a pull request:

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


        
相关标签:
3条回答
  • 2020-12-03 11:41

    The primary reason to use ok_or_else or any of the ..._or_else methods is to avoid executing a function when it's not needed. In the case of Option::ok_or_else or Option::unwrap_or_else, there's no need to run extra code when the Option is Some. This can make code faster, depending on what happens in the error case

    In this example, Error::new likely performs allocation, but it could also write to standard out, make a network request, or anything any piece of Rust code can do; it's hard to tell from the outside. It's generally safer to place such code in a closure so you don't have to worry about extraneous side effects when the success case happens.

    Clippy lints this for you as well:

    fn main() {
        let foo = None;
        foo.unwrap_or("hello".to_string());
    }
    
    warning: use of `unwrap_or` followed by a function call
     --> src/main.rs:3:9
      |
    3 |     foo.unwrap_or("hello".to_string());
      |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| "hello".to_string())`
      |
      = note: `#[warn(clippy::or_fun_call)]` on by default
      = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#or_fun_call
    
    0 讨论(0)
  • 2020-12-03 11:51

    The signature of std::io::Error::new is

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

    This means that Error::new(ErrorKind::Other, "Decode error") allocates memory on the heap because error needs to be converted into Box<Error + Send + Sync> to be of any use.

    Consequently, this pull request removes unneeded memory allocation/deallocation when the Result value is Result::Ok.

    0 讨论(0)
  • 2020-12-03 11:57

    In addition to performance implications, more complex arguments in ok_or might yield unexpected results if one is 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 would have been avoided with ok_or_else (and the same goes for other *_or_else functions), because the closure is not evaluated if the variant is Some.

    0 讨论(0)
提交回复
热议问题