What is actually moving a variable in Rust?
Asked Answered
J

2

6

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. :)

Jurkoic answered 22/3, 2022 at 17:49 Comment(0)
L
4

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.)

Lindseylindsley answered 22/3, 2022 at 17:58 Comment(5)
mmm... so, it would be: it depends, hahaha, but, since I'm a beginner, this is a bit difficult for me, but considering what you tell me (I haven't configured anything, only using cargo run hahaha ), practically the variable s1 is not initialized, that is, it is not given a value in memory, since the value is initialized in s2, that is, it "transforms" it or understands it like this: let s1; let s2 = String::from("hello"); println!("{}, world!", s2); . so there is no reference, shallow copies in s1, am I understanding correctly? hahaha.Jurkoic
@Jurkoic Conceptually, a move takes the value in one variable and transfers ownership to another. It's not really any more complicated than that. How the data gets from one variable to another (and which variables can be optimized away) is a detail for the compiler to figure out. In this case, s1 owns a String which is a managed pointer to a heap allocation. Moving it to s2 transfers ownership of the String value (and hence the heap allocation) to s2, but a move does not require that there even is a heap allocation.Lindseylindsley
aaaaahhh!!!!, I think now I understand, it's moving the allocation, is it just moving the pointer or ownership?, but not necessarily re-allocating or allocating again in the heap, but I stay with what you tell me, I find it difficult to understand some words and concepts, since I do not speak English (I speak Spanish) and many words that are different are translated for me as if they had the same meaning and I do not find differences between some things, Thanks for your time.Jurkoic
@Grizzly: Yep. move semantics are only meaningful when there is some other RAII-controlled resource involved (e.g. heap allocation, where the resource must be released exactly once) or must have unique ownership (e.g. mutable references can't be duplicated without risking race conditions). Stuff that doesn't involve resource management of some form typically implements the Copy trait so moves instead become copies, because there's no benefit to moving (copying an i64 costs the same as moving it)/no additional cost to copying.Pettis
@Grizzly: And to be clear, it's definitely not reallocating anything on the heap; a major benefit of move semantics is that any such heap allocations transfer from one to the other. If it was making a new allocation and copying from old to new, then deleting the old, move semantics would provide little benefit (you'd still do all the work of cloning the object, your peak memory usage would still double, you'd just free half of it faster due to the automatic deletion, a minor benefit). Why risk allocation failures, require twice as much allocation/cleanup work, etc., when you can avoid it?Pettis
F
1

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?

It means you, as developer, cannot access s1 anymore. So, from your point of view, you can think s1 doesn't exist.

Does it have any value?

No, because it doesn't exist (from your point of view).

does it really move it?

Depends on the program and the compiler. Conceptually, it does.

or is there still any value in s1 in memory?

Even without talking about moving, variables in general may never be in memory to begin with! It depends on what the optimizer does.

this check happens at compile time

Yes, it 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.

No, it depends. Both, one or none of them may exist in memory, caches, registers... To know you need to check the particular case.

Ferrule answered 22/3, 2022 at 21:19 Comment(3)
but, based on the example in the documentation: fn main() { let s = String::from("hello"); is_free(s); println!("Other Code..."); // at this point, has the variable "s" already been removed from memory? println!("Other Code..."); println!("Other Code..."); } fn is_free(some_string: String) { println!("{}", some_string); } Since the variable s was "moved", was it removed from memory when it exited the scope of the is_free function? Or was the variable s removed from memory when the scope of the main function ended?Jurkoic
Memory in the heap is removed at the end of is_free (though optimizers could in principle avoid allocation...), but s is different. It's likely to be in memory or somewhere else until end of main. But you cannot use it even if it is there.Ferrule
@Grizzly: To be clear, when Acorn says "s is different", they mean the trivial amount of memory used to store the "metadata" about the string (the length and a pointer to the actual data, where the actual data is likely allocated on the heap). s itself (not that string info it points to) is typically stack allocated (or even kept solely in registers) and most compilers reserve stack for locals on function entry and release it on return; there'd be stack memory reserved for the s "metadata" the whole time, it's just unused after calling is_free; there's no benefit to cleaning it early.Pettis

© 2022 - 2024 — McMap. All rights reserved.