为什么我更喜欢`Option :: ok_or_else`而不是`Option :: ok_or`?

时间:2017-08-07 12:47:27

标签: rust

我刚看到拉取请求中的以下更改:

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

我所知道的唯一区别是:

  1. ok_or我们已经Error创建了Error::new并将其传递给了适配器。
  2. ok_or_else中,我们传递了一个闭包,它会生成这样的值,但如果Some中有Option个数据,则可能无法调用。
  3. 我错过了什么吗?

3 个答案:

答案 0 :(得分:13)

使用ok_or_else任何 ..._or_else方法的主要原因是避免在不需要时执行函数。如果是Option::ok_or_elseOption::unwrap_or_else,则OptionSome时无需运行额外代码。这可以使代码更快,具体取决于错误情况

中发生的情况

在这个例子中,Error::new可能执行分配,但它也可以写入标准输出,发出网络请求,或任何Rust代码可以做的任何事情;从外面很难说出来。将这些代码放在一个闭包中通常更安全,因此当成功案例发生时你不必担心无关的副作用。

Clippy也为你提供了这个:

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

答案 1 :(得分:3)

std::io::Error::new的签名是

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

这意味着Error::new(ErrorKind::Other, "Decode error")在堆上分配内存,因为error需要转换为Box<Error + Send + Sync>才有用。

因此,当Result值为Result::Ok时,此拉取请求会删除不需要的内存分配/释放。

答案 2 :(得分:3)

除了性能影响之外,ok_or中更复杂的参数如果不够小心,可能会产生意外结果;考虑以下情况:

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
}

ok_or_else可以避免这种情况(对于其他*_or_else函数也是如此),因为如果变量是Some,则不会对闭包进行求值。