即使未执行,变量也会被移至闭包

Variable is moved to closure even when it is not executed

我有一个函数可以 return 只有一个错误,所以我想创建一次,如果需要的话 return 该实例。

这里有一个例子来演示:

fn foo() -> Result<(), String> {
    let err = String::from("error message");
    let a = function_that_returns_an_option().ok_or_else(|| err)?;
    let b = function_that_returns_another_option().ok_or_else(|| err)?;
    // ...do something with a and b...
    Ok(())
}

这会导致以下编译错误:

error[E0382]: use of moved value: `err`
  --> src/main.rs:12:61
   |
10 |     let err = String::from("error message");
   |         --- move occurs because `err` has type `String`, which does not implement the `Copy` trait
11 |     let a = function_that_returns_an_option().ok_or_else(|| err)?;
   |                                                           -- --- variable moved due to use in closure
   |                                                           |
   |                                                           value moved into closure here
12 |     let b = function_that_returns_another_option().ok_or_else(|| err)?;
   |                                                                ^^ --- use occurs due to use in closure
   |                                                                |
   |                                                                value used here after move

我以为移动是懒惰发生的,但如果我理解正确,err 被移动到第一个闭包,即使闭包没有执行,因此它不能在第二个闭包(或编译器)中使用不知道并拒绝它只是为了安全?)。我可以用 err.clone() 替换闭包中的 err,但这有点违背了目的。是否有另一种更“惯用”的方式来做到这一点?

要拥有一个可以以“动态检查”方式移出的变量,您可以将值包装在 Option 中并使用 Option::take:

let mut err = Some(String::from("error message"));
let a = function_that_returns_an_option().ok_or_else(|| err.take().unwrap())?;
let b = function_that_returns_another_option().ok_or_else(|| err.take().unwrap())?;

(需要 .unwrap(),因为 .take() returns Option<String>,因为该值可能已被占用。)

但是,无论如何,这不是错误处理的最佳选择,因为即使函数成功,您也会分配错误字符串。有一种直接的方法可以避免这种情况并进一步简化代码:将分配放在闭包中,并重用闭包而不是消息值:

let err = || String::from("error message");
let a = function_that_returns_an_option().ok_or_else(err)?;
let b = function_that_returns_another_option().ok_or_else(err)?;