Search code examples
rustmove

Understanding subtlety of move semantics involving struct


I constructed this code example of seemingly inconsistent effect of what (is|is not) considered a move and hence (is|is not) allowed to compile in Rust.

struct Foo {
    bar: String,
    bazs: Vec<String>,
}


fn main() {
    let a: Foo = Foo {
        bar: "bar".to_string(),
        bazs: vec!["baz_1".to_string(), "bax_2".to_string()],
    };
    
    let b: String = a.bar;
    
    println!{"Who owns the String value, variable a or b? {b}"};
    
    
    // assigning a String (from a field of String type) to b IS NOT considered a move
    // assigning a String (from an element of field of Vec<String> type to IS considered a move
    // let c: String = a.bazs[0];
}

Rust playground

The example is based on the reading of the book of the Rust Programming language, in relation to thess sections where the move of Strings are discussed, but does not provide the sufficient reasoning for me to understand this constructed example yet:

I hope to get some pointers on how to correctly understand this issue.


Solution

  • Rust has partial moves. You can move both a.bar and a.bazs out of a separately. What you can't do is use a in its entirety after moving any of its (non-Copy) fields out. But the individual unmoved fields are still fair game. So this is fine:

    let a: Foo = Foo {
        bar: "bar".to_string(),
        bazs: vec!["baz_1".to_string(), "bax_2".to_string()],
    };
    
    let b = a.bar;
    let bazs = a.bazs;
    

    While this is not:

    let a: Foo = Foo {
        bar: "bar".to_string(),
        bazs: vec!["baz_1".to_string(), "bax_2".to_string()],
    };
    
    let b = a.bar;
    let new_a = a;
    

    You'll see

    error[E0382]: use of partially moved value: `a`
    partial move occurs because `a.bar` has type `std::string::String`,
    which does not implement the `Copy` trait