0

Edit: added a second part of the question below.

I am getting the error "borrow of moved value" here, but the thing being moved is an &mut Containee, so I didn't expect it to cause a problem. The similar portion in the commented-out code just under it feels like it's doing basically the same thing. Can someone explain to me why the commented-out portion works, but the uncommented portion doesn't?

struct Container<'c>(&'c mut Containee);

enum Containee {
    A(u32),
    B(u32),
}

fn main() {
    let mut a = Containee::A(5);
    let container = Container(&mut a);

    match container {
        Container(inner @ Containee::A(num)) => {
            *inner = Containee::B(*num);
        }
        /* But this works
        Container(inner) => {
            if let Containee::A(num) = inner {
                *inner = Containee::B(*num);
            }
        }
        */
        _ => {}
    }
}

This produces the following error. I tried following all the suggestions around explicitly binding the subpattern in different ways (even though they didn't seem likely to help), and indeed they didn't end up helping.

error: borrow of moved value
  --> src\main.rs:13:19
   |
13 |         Container(inner @ Containee::A(num)) => {
   |                   ^^^^^                --- value borrowed here after move
   |                   |
   |                   value moved into `inner` here
   |                   move occurs because `inner` has type `&mut Containee`, which does not implement the `Copy` trait
   |
help: borrow this binding in the pattern to avoid moving the value
   |
13 |         Container(ref inner @ Containee::A(num)) => {
   |                   +++

Edit: thank you for the responses so far. In my actual use case, Containee holds something that isn't Copy, namely a String. The commented-out "if let" method works for that, but not the subpattern match, even the suggested fix in the answer.

struct Container<'c>(&'c mut Containee);

enum Containee {
    A(String),
    B(String),
}

fn main() {
    let mut a = Containee::A(String::from("hi"));
    let container = Container(&mut a);

    match container {
        Container(inner @ &mut Containee::A(data)) => {
            *inner = Containee::B(data.clone());
        }
        /* But this works
        Container(inner) => {
            if let Containee::A(data) = inner {
                *inner = Containee::B(data.clone());
            }
        }
        */
        _ => {}
    }
}

I know that inner is overwritten completely without anything other than the bound variable data being read. Is there a way to convince Rust about that too?

I get this error:

error[E0507]: cannot move out of `container` as enum variant `A` which is behind a mutable reference
  --> src\main.rs:12:11
   |
12 |     match container {
   |           ^^^^^^^^^
13 |         Container(inner @ &mut Containee::A(data)) => {
   |                                             ----
   |                                             |
   |                                             data moved here
   |                                             move occurs because `data` has type `String`, which does not implement the `Copy` trait
   |
help: consider removing the mutable borrow
   |
13 -         Container(inner @ &mut Containee::A(data)) => {
13 +         Container(inner @ Containee::A(data)) => {
   |
1
  • 1
    This: Container(inner @ &mut Containee::A(num)) works: playground. Commented Sep 29 at 6:48

1 Answer 1

3

I think the problem with your code is that inner is a mutable reference to the whole Containee, and num is a reference to the u32 inside the same Containee, so you have both a mutable and an immutable reference to the same Containee at the same time, which is forbidden.

If you write Container (inner @ &mut Containee::A (num)), it will work because then num is a plain u32 which is copied from a before the inner mutable reference is taken:

struct Container<'c>(&'c mut Containee);

enum Containee {
    A(u32),
    B(u32),
}

fn main() {
    let mut a = Containee::A(5);
    let container = Container(&mut a);

    match container {
        Container(inner @ &mut Containee::A(num)) => {
            *inner = Containee::B(num);
        }
    }
}

Playground

(I'm not sure why the if let variant works though).

Sign up to request clarification or add additional context in comments.

2 Comments

I'm 90% sure the if let works because of NLL: Rust can statically see that the lifetimes of inner (as an r-value) and num don't have to overlap, so it can end the borrow of inner once num’s lifetime begins. In the original @ match statement, inner and num necessarily have overlapping lifetimes. If you add the pointless statement &mut inner; inside the if let, NLL can no longer cut short the lifetime of inner, and you get the same borrow error as the @ match.
Thanks for this. Your explanation makes sense. I edited my question with an additional case, in case you have ideas there.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.