Illegal move, but I drop on the next line

Hey guys, I have this situation where I have optional structs that include other optional structs. And I am trying to move and Option to a new place, but accidentally, I introduce sharing of a reference and the compiler complains:

line: some_bar.string = Some(*string);

cannot move out of `*string` which is behind a shared reference
move occurs because `*string` has type `String`, which does not implement the `Copy` trait
consider cloning the value if the performance cost is acceptable: `.clone()`

But - on the next line I do a drop of the value that the reference string points to:

some_bar.baz = None;

Full code:

struct Foo {
    bar: Option<Bar>
}

struct Bar {
    baz: Option<Baz>,
    string: Option<String>,
}

struct Baz {
    string: Option<String>,
}

fn example(foo: Foo) {
    let mut foo = Foo { bar: None };
    match foo.bar {
        None => (),
        Some(ref mut some_bar) => {
            match some_bar.baz.as_mut() {
                None => (),
                Some(some_baz) => {
                    match &some_baz.string {
                        None => (),
                        Some(string) => {
                            some_bar.string = Some(*string);
                            some_bar.baz = None;
                        }
                    }
                }
            }
        }
    }
}

Can you please explain to me, why am I wrong? I hoped that the compiler would see that I am dropping the container which contains the value referenced in string and thus would allow me to do the move...

You can use Option::take instead of dereferencing and thus moving the string variable. Example.

3 Likes

Thank you for the suggestion, and it will work well enough with Option, yes, but it doesn't work well when it is not an Option.

Consider this:


struct Foo {
    bar: Option<Bar>
}

struct Bar {
    baz: Option<Baz>,
    result_strings: Vec<String>,
}

struct Baz {
    temporary_string_storage: Vec<String>,
}

fn example(foo: Foo) {
    let mut foo = Foo { bar: None };
    match foo.bar {
        None => (),
        Some(ref mut some_bar) => {
            match some_bar.baz.as_mut() {
                None => (),
                Some(some_baz) => {
                    some_bar.result_strings = some_baz.temporary_string_storage;
                    some_bar.baz = None;
                }
                
            }
        }
    }
}

On line some_bar.result_strings = some_baz.temporary_string_storage; it says:

cannot move out of `some_baz.temporary_string_storage` which is behind a mutable reference
move occurs because `some_baz.temporary_string_storage` has type `Vec<String>`, which does not implement the `Copy` trait
consider cloning the value if the performance cost is acceptable: `.clone()`

The container is still optional, so you can take ownership via take of the container, instead of the now not-optional field.

Edit: Somebody created a similar topic today, you might find the answers there useful, too.

I'd write that last example this way.

 fn example(foo: Foo) {
     let mut foo = Foo { bar: None };
     match foo.bar {
         None => (),
         Some(ref mut some_bar) => {
-            match some_bar.baz.as_mut() {
+            match some_bar.baz.take() {
                 None => (),
                 Some(some_baz) => {
                     some_bar.result_strings = some_baz.temporary_string_storage;
-                    some_bar.baz = None;
                 }
             }
         }
     }
 }
2 Likes

Perhaps these examples are too contrived to reflect your actual situation. In which case, you could check out the replace_with crate. It allows you to temporary move out of a place, so long as you provide some way to reinitialize that place later (or abort to avoid UB).

Actually, there where I have match on some, I will have additional checks. Basically, I don't always need to take, but only on some condition.

Perhaps these examples are too contrived to reflect your actual situation.

No, they are not, it's a big and complex state machine, it's a AST parser...