'"use of moved value" when the control flow won't allow it?

This is a minimal reproduction of the code I'm struggling with:

// real code struct is not just a string, and is not cloneable
struct NotCloneable(String);

fn outer() -> Result<NotCloneable, String> {
    let obj = NotCloneable(String::from("Hello, world!"));
    might_fail().map_err(|_| {
        // real code closure might return one of several errors, including an
        // error containing non-cloneable fields from struct
        obj.0
    })?;
    // the ? means that if we got to here, the closure never executed
    // ... yet this won't compile because it claims the closure moved obj
    Ok(obj)
}

fn might_fail() -> Result<(), ()> {
    Err(())
}

It fails to compile with:

error[E0382]: use of partially moved value: `obj`
  --> src/main.rs:16:8
   |
10 |     might_fail().map_err(|_| {
   |                          --- value partially moved into closure here
...
14 |         obj.0
   |         ----- variable partially moved due to use in closure
15 |     })?;
16 |     Ok(obj)
   |        ^^^ value used here after partial move
   |
   = note: partial move occurs because `obj.0` has type `String`, which does not implement the `Copy` trait

For more information about this error, try `rustc --explain E0382`.
error: could not compile `playground` due to previous error


Solution 1:[1]

After @Jmb's comment I also tried:

fn outer() -> Result<NotCloneable, String> {
    let obj = NotCloneable(String::from("Hello, world!"));
    match might_fail() {
        Ok(_) => Ok(()),
        Err(_) => Err(obj.0),
    }?;
    Ok(obj)
}

However, this fails to compile due the same error. What I did find is that if I explicitly early-return then it works:

fn outer() -> Result<NotCloneable, String> {
    let obj = NotCloneable(String::from("Hello, world!"));
    match might_fail() {
        Ok(_) => (),
        Err(_) => return Err(obj.0),
    };
    Ok(obj)
}

So it seems that whatever control flow analysis is happening is not smart enough to figure out that the capture is in an Err that is handled with an early return '?'.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Tom Kludy