3

The following code compiles:

fn consume(_: Box<u64>) {}
let tuple = (Some(Box::new(1)), Some(Box::new(2)));
match tuple {
    (Some(x), Some(y)) => {
        consume(x);
        consume(y);
    }
    _ => (),
}

The following code compiles:

fn consume(_: Box<u64>) {}
match [Some(Box::new(1)), Some(Box::new(2))] {
    [Some(x), Some(y)] => {
        consume(x);
        consume(y);
    }
    _ => (),
}

But this code does not compile:

fn consume(_: Box<u64>) {}
let array = [Some(Box::new(1)), Some(Box::new(2))];
match array {
    [Some(x), Some(y)] => {
        consume(x);
        consume(y);
    }
    _ => (),
}

The compiler says:

error[E0382]: use of moved value: `(array[..] as std::prelude::v1::Some).0`
 --> src/main.rs:5:24
  |
5 |         [Some(x), Some(y)] => {
  |               -        ^ value used here after move
  |               |
  |               value moved here
  |
  = note: move occurs because the value has type `std::boxed::Box<u64>`, which does not implement the `Copy` trait

Why do the first and second version compile, but not the third version?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
xardas
  • 355
  • 1
  • 6

2 Answers2

3

It is unexpected that the third version does not compile. The same issue occurs when matching boxed values as described here.

Citing the explanation for the error from the linked answer:

My only guess is that the ownership of the Box is moved to the first param, the param is extracted, then the compiler tries to move it again to the next parameter.

Replace "Box" with "array" and you get an explanation for what is going on when matching an array. One of the solutions presented in the linked answer also works for matching arrays - the use of curly braces to force a complete move of the Box/array into the match expression:

fn consume(_: Box<u64>) {}
let array = [Some(Box::new(1)), Some(Box::new(2))];
match {array} {
    [Some(x), Some(y)] => {
        consume(x);
        consume(y);
    }
    _ => (),
}
Calculator
  • 2,769
  • 1
  • 13
  • 18
3

Here's a reduced version of your code:

struct NonCopy;

fn main() {
    // OK
    let tuple = (Some(NonCopy), Some(NonCopy));
    if let (Some(_x), Some(_y)) = tuple {}

    // OK
    if let [Some(_x), Some(_y)] = [Some(NonCopy), Some(NonCopy)] {}

    // Fails
    let array = [Some(NonCopy), Some(NonCopy)];
    if let [Some(_x), Some(_y)] = array {}
}

The good news

This code works as-is when non-lexical lifetimes are enabled.

The bad news

Non-lexical lifetimes aren't stable yet.

The workaround

Explicitly transfer ownership of the array to the match or if let head expression:

let array = [Some(NonCopy), Some(NonCopy)];
if let [Some(_x), Some(_y)] = { array } {}

The explanation

The current implementation of the borrow checker is AST-based while a future implementation will be MIR-based. At a high level, you can think of this as "working on the code as I typed it" (AST) and "working on the logical flow of data in my code" (MIR).

Certain "hacks" have been added to the AST borrow checker, which is why you can successfully use an array literal but not the variable. With the MIR borrow checker, bigger hacks like these will disappear and the borrow checker will also become more precise, allowing more code to compile.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366