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

34

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

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

我知道的唯一区别如下:
  1. ok_or 中,我们已经通过 Error::new 创建了 Error 并将其传递给了一个适配器。
  2. ok_or_else 中,我们传递了一个闭包,它将产生这样的值,但如果在 Option 中有 Some 数据,则可能不会调用该闭包。
我有遗漏吗?
3个回答

49

使用ok_or_else或者任何一个..._or_else方法的主要原因是避免在不需要的情况下执行函数。在Option::ok_or_else或者Option::unwrap_or_else中,当OptionSome时,没有必要运行额外的代码。这可以根据错误情况发生的事情来使代码更快。

在这个例子中,Error::new很可能会执行分配,但它也可以写入标准输出,进行网络请求或任何Rust代码的操作;从外部很难判断。通常更安全的做法是将这样的代码放在一个闭包中,这样当成功的情况发生时,您就不必担心额外的副作用。

Clippy也会对此进行提示:

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

2
好的,但你能告诉我哪些情况适合使用unwrap_orunwrap_or_else以及其他情况吗?我知道有一些情况是当你将它们与标量一起使用时,例如像unwrap_or(0),但这就是全部吗? - Victor Polevoy
1
@VictorPolevoy 或者任何你已经计算过该函数的情况,无论出于什么原因;它不必是像整数这样的“简单”类型。 - Shepmaster

9

除了性能影响外,在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,则不会评估闭包。

这与我的“但它也可以写入标准输出”行有何不同? - Shepmaster
2
@Shepmaster,它关注的是不同的事情——你的答案的初始形式听起来大多是以性能为导向的,可能会对浏览读者产生误导性的结果。 - ljedrz

4
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时移除了不必要的内存分配/释放。

1
严谨地说,并不“需要”实际调用into,因此不能保证一定会发生分配。有可能存在某些情况下不会发生分配。但是,这的确非常可能发生。 - Shepmaster

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接