dowjones123
dowjones123

Reputation: 3837

Move of a struct variable is a "move" but new space in memory is allocated and addresses of underlying values are different

I'm trying to understand Rust's move semantics in the case of a struct that contains fields with types that implement the Copy trait. Consider the following struct:

struct Book {
    author: &'static str,
    title: &'static str,
    year: u32,
}

fn main() {
    let bookA = Book {
        author: "Douglas Hofstadter",
        title: "Gödel, Escher, Bach",
        year: 1979,
    };
    
    let addr_bookA: *const Book = &bookA;
    let bookB = bookA; // Move occurs here
    
    // Getting the memory addresses as raw pointers
    
    let addr_bookB: *const Book = &bookB;
    
    println!("Address of bookA: {:p}", addr_bookA);
    println!("Address of bookB: {:p}", addr_bookB);
}

Output:

Address of bookA: 0x7fff20b1a0b0
Address of bookB: 0x7fff20b1a0e0

Given there is a move and bookB takes up the value that was assigned to bookA, I was expecting a shallow copy of the data when bookA is assigned to bookB. However, when I check the memory addresses of these two variables, they are different.

Could someone help clarify the following points?

  1. Why does a move of a struct still result in different memory addresses for the original and the moved-to variables?

  2. Is the data actually copied in this scenario, and if so, why does Rust treat it as a move?

Playground

Upvotes: 3

Views: 150

Answers (2)

cdhowie
cdhowie

Reputation: 169018

Why does a move of a struct still result in different memory addresses for the original and the moved-to variables?

Quite simply, because you observed the addresses. This forces the optimizer to realize two different locations to hold their data; the very act of obtaining the addresses prevents the optimizer from eliding the move. (In your case you print them out, but the optimizer likely is unable to figure out that the function you hand them off to to perform the formatting isn't going to dereference them.)

As Professor Farnsworth once said:

No fair! You changed the outcome by measuring it

If you remove the code that obtains and prints the addresses, and then inspect the resulting assembly, you will see that there is no code generated for the move. (The highlighting here is misleading, because the line let bookB = bookA shows generated code, but note that the line initializing bookA does not. If you remove the line with the move, the generated assembly will be identical. It seems the optimizer is simply delaying initialization until the line with the move, and so it associates the generated assembly with that line instead of with the line initializing bookA.)

If you are curious what optimizations are being performed, you should inspect the resulting assembly directly. Modifying your code in an attempt to determine what optimizations are being performed can change which optimizations are performed.

Upvotes: 5

Masklinn
Masklinn

Reputation: 42292

Is the data actually copied in this scenario, and if so, why does Rust treat it as a move?

Because the only difference between a move and a copy is that with a copy the original can still be used. Semantically, both are memcpy (so byte-copy of the source to the target).

Those copies may be optimised away, but that's independent of the semantics.

Why does a move of a struct still result in different memory addresses for the original and the moved-to variables?

Because LLVM does not optimise away the copy. I will confess that I don't know why, I used to assume LLVM would not see the difference between an SSA version of the same thing but clearly it does and either refuses or can not optimise its stack allocation by removing the copy (just relabelling internally). I've never dived into the reason why that could not happen tho, there might be some metadata which rustc is not setting, or some safety corner case which prevents LLVM from doing it.

Upvotes: 6

Related Questions