Reputation: 3837
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?
Why does a move of a struct still result in different memory addresses for the original and the moved-to variables?
Is the data actually copied in this scenario, and if so, why does Rust treat it as a move?
Upvotes: 3
Views: 150
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:
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
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