Search code examples
rustclosuresownership

Variable is moved to closure even when it is not executed


I have a function that can return only one error, so I want to create it once and return that instance if needed.

Here's an example to demonstrate:

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(())
}

Which results in the following compilation error:

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

I thought the move happens lazily, but if I understand correctly, err is moved to the first closure even if the closure is not executed and therefore it cannot be used in the second closure (or the compiler does not know that and rejects it just to be safe?). I can replace the err in the closures with err.clone(), but that defeats the purpose a little bit. Is there maybe another more "idiomatic" way to do that?


Solution

  • To have a variable that you can move out of in a “dynamically checked” fashion, you can wrap the value in Option and use 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())?;
    

    (The .unwrap() is needed because .take() returns Option<String>, since the value might already have been taken.)

    However, this isn't the best option for error handling, anyway, because you're allocating the error string even if the function succeeds. There's a straightforward way to avoid this and also simplify the code further: put the allocation inside the closure, and reuse the closure instead of the message value:

    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)?;