rustmove-semantics

Does Rust's move semantics involve copying data?


I am curious about the "move semantics" in Rust and whether data is copied when ownership is transferred. Here's some demo code:

#[derive(Debug)]
struct Foo {
    name: i32,
    age: i32,
}

fn main() {
    let foo = Foo { name: 111, age: 1 };

    let ptr_foo = std::ptr::addr_of!(foo);
    println!("Address of foo: {:p}", ptr_foo);

    let bar = foo;

    let ptr_bar = std::ptr::addr_of!(bar);
    println!("Address of bar: {:p}", ptr_bar);
}

I initially thought that "there's a variable foo, and after a move, we simply renamed it to bar", thereby avoiding the need to copy its corresponding data.

To investigate further, I used the debug functionality in VSCode with a plugin called "Hex Editor" and found that both foo and bar ( memory addresses 0x7fffffffd7d8 and 0x7fffffffd828), containing identical data (6F 00 00 00 01 00 00 00).

enter image description here

Does this indicate that Rust actually performs a copy operation even during a move, such as copying a struct in this case? Could this behavior vary depending on whether it's in release mode?


Solution

  • "Move" in many languages (not just Rust) is a semantic term that refers to transfer of ownership and does not necessarily dictate a mechanism for that transfer. In Rust, moving a value copies the data somewhere else, but leaves the source of the value unusable (unless that source is later reinitialized with a valid value, or the type being moved implements Copy).

    How this is implemented in Rust is with a bitwise copy from the source value to the destination. At higher optimization levels, this copy can be elided, but may not be.

    Note that the copy only extends to the values contained directly within the value being moved. For example, when moving a Vec this copies the data pointer, length, and capacity of the Vec but not the actual elements of the Vec, which are behind that pointer. This allows you to move any Vec at the same cost -- moving a huge Vec and an empty Vec both require copying exactly the same amount of stuff (3 pointer-sized values).

    In your code, taking the address of foo and bar is likely preventing the optimizer from actually eliding the copy, because it cannot unify their memory location. Doing so and returning the same address for both variables would violate the as-if rule.

    In other words:

    If you are trying to see how code will compile, don't change that code to inspect values from inside of the code or you will almost certainly change the optimizer's behavior in some way. Instead, look at the generated machine code.