rustcloneownershipshallow-copyowner

What is actually moving a variable in Rust?


I have never experimented with languages like C, C++, Go, etc., and I decided to start with Rust, I already understand a little what the stack and the Heap are, but, what does it really mean by moving a variable, the documentation says that it is not a shallow copy:

... probably sounds like making a shallow copy. But because Rust also invalidates the first variable, instead of calling it a shallow copy, it’s known as a move. In this example, we would say that s1 was moved into s2...

For example:

 let s1 = String::from("hello");
 let s2 = s1;

 println!("{}, world!", s1);

What does the documentation mean when it says "invalidates". Does this mean that Rust invalidates s1 and assigns the value that was in s1 to s2, so... s1 doesn't exist? o Does it have any value?, that's mainly what I don't understand, does it really move it? or is there still any value in s1 in memory?

From what I could understand, this check happens at compile time, so it makes me think that s1 literally doesn't exist in memory and only s2, since s1 was literally moved to s2.

Obviously this happens with values that have an unknown size, that is, in the heap.

I hope you can help me understand. :)


Solution

  • The answer is both yes and no to pretty much every yes/no question you asked. There's two different contexts we can answer this in: the semantics of the language, and what the compiler does with the code.

    Semantically, s1 can no longer be read after the value is moved into s2. It can be initialized to a new String value and then used again. In a manner of speaking, "uninitialized" and "moved from" are effectively the same state in that you must assign something to the variable before you can use it. (They are a bit different in the messages that the compiler gives, and in that sub-values of a struct can be moved-from, but can't be uninitialized.)

    What will the compiler do with this? Well, that can depend on many factors, including (but not limited to) the version of the compiler and the optimization level selected. If the compiler wishes to, it can completely elide the move and therefore s2 in effect becomes just another name for the same region of memory as s1. It could also give s2 its own region of memory on the stack and blit the contents of s1 into it. Any specific answer about what the compiler does would have to be qualified with a complete description of the compilation environment, compiler version, and the flags given to the compiler.

    Obviously this happens with values that have an unknown size, that is, in the heap.

    Any owned (and unpinned) value can be moved, regardless of whether it manages an allocation on the heap. Typically, the heap allocation isn't moved itself. The pointer to the heap allocation is moved to its new location. (For example, moving a String or a Vec won't change the memory location where the actual contents are stored. The pointer to that data is just changing hands.)