Understanding move when using match inside a loop

Consider the code:

enum Bar {
    Baz(String),
    Ham,
}

struct Foo {
    bar: Bar,
}

pub fn main() {
    let foo = Foo { bar: Bar::Baz(String::from("abc")) };
    
    for _ in 0..3 {
        match foo.bar {
            // Compiler complains that value was moved here.
            // This match, therefore, is unlikely to trigger again
            // in next iteration(s).
            Bar::Baz(s) => {
                match s.as_str() {
                    "abc" => (),
                    _ => ()
                }
            },
            _ => ()
        }
    }
}

The compiler complains:

error[E0382]: use of moved value
  --> src/main.rs:15:22
   |
15 |             Bar::Baz(s) => {
   |                      ^ value moved here, in previous iteration of loop
   |
   = note: move occurs because value has type `std::string::String`, which does not implement the `Copy` trait

As noted in the comment within code:

Compiler complains that value was moved here. This match, therefore, is unlikely to trigger again in next iteration(s).

Seems like my inference is incorrect and I am not clear about what's happening here. Would appreciate an explanation.

Because you match on the enum, the content is moved out. If you match on the same value over, and over again it will keep trying to move something that is already gone. If you add ref in front of s, so it is Bar::Baz(ref s) then it will only reference and not move the inner value. It compiles fine then. (see playground for version with ref)

Normally in a loop I would think you would match on a different value (maybe from an array or iterator?) each time and this would work. In this case, you are always matching the same value, so why not move it outside the loop, just do it once, and then the compiler won't complain that you keep trying to move it?

1 Like

I mean the issue is that by using foo.bar, you are taking ownership of foo which destroys the foo value. Since this destroys it, it is not accessible in the next iteration, which you can fix by borrowing it instead by matching on &foo.bar (or by adding ref to the matched case).

1 Like

Is it possible to just take ownership of bar in foo.bar without taking ownership of foo?

No, foo cannot exist without a value in the bar field. You can swap out the value with another one using mem::replace, or you can just borrow the field.

1 Like

Thanks! Does std::mem::replace have any advantages over simply doing clone() to avoid borrow checker errors?

One advantage is that you can use mem::replace( &mut some_string, String::new() ) to temporarily take a String by value without allocating memory.

I assume you don't want to take ownership forever, otherwise what's the point of the Foo struct? If you want to mutate bar, you can just take a mutable reference with let bar = &mut foo.bar;.