Weird `use of moved value` behaviour

code from the above playground:

struct NoCopy;

fn this_one_compiles() {
    let a = Some(5);
    let b = Some(NoCopy);
    
    loop {
        if let Some(a) = a && let Some(b) = b {
            break (a, b)
        }
    };
}

fn then_why_doesnt_this_one_compile() {
    let a = Some(NoCopy);
    let b = Some(NoCopy);
    
    loop {
        if let Some(a) = a && let Some(b) = b {
            break (a, b)
        }
    };
}

the first function compiles and shows that rust understands that if let Some(foo) = opt { break foo },
the second function shows that it doesn't.
my guess is that only the last let can consume, but why?

if let Some(myOptionValue) = myOption effectively moves the value out from the option.

The reason why it works on the first function but doesn't in the second, is that you're dealing with two variables, and both need to be Some for the loop to break.

For the first function, if the value of a moves out, it doesn't matter, because it's copy, so the loop can continue until b is Some.

This doesn't work for the second function, because as soon as the value of a moves out in one of the loop iterations, the loop can't keep going. That is, think of the case of a being Some and b being None.

oh, i thought it'd first check if all the patterns match (which just requires references), then move all values at once once (not a typo) the branch is taken.

my current work around is

if opt_1.is_some() && opt_2.is_some() && ... opt_n.is_some() {
    break (opt_1.unwrap(), opt_2.unwrap(), ... op_n.unwrap());
}

are there any less bad work arounds?

Here's one option.

    let mut a = Some(NoCopy);
    let mut b = Some(NoCopy);
    
    loop {
        match (a, b) {
            (Some(a), Some(b)) => break (a, b),
            (a2, b2) => (a, b) = (a2, b2),
        }
    };

You could zip them before entering the loop:

let zipped = a.zip(b);

    loop {
        if let Some((a, b)) = zipped {
            break (a, b)
        }
    };

wait, you can assign existing variables using patterns‽
i always thought patterns could only create new variables.

that solution works, thanks.

It's not a pattern per se (or as you say, a and b would be new, distinct bindings (variables), and not overwrite existing variables). It's a destructuring assignment. They were stabilized in Rust 1.59 (Feb. 2022).