Semantics of moves of owned pointers - rust

This article seems to imply the possibility that the use of the term "move" in the rust documentation doesn't mean copies, but transfer of ownership at compile time. See this quote specifically:
The compiler enforces that there is only a single owner. Assigning the pointer to a new location transfers ownership (known as a move for short). Consider this program:
Is this correct? are ownership transfers/moves not actually copies at runtime, but only a compile time abstraction.

No, a move is still a copy (in the sense of memcpy) although not necessarily of the whole data structure. However, it has the compile-time semantics that you list. That is,
let a = ~[1,2,3];
let b = a; // copies one word (the pointer), "moves" the data.
let c = b.clone(); // copies the data too.
(Note that I've used b.clone() rather than copy b, because Copy is being removed, and replaced by Clone, which is more powerful/flexible.)
This copying behaviour is required to happen, because (many) variables in Rust are definite chunks of memory (just like those in C/C++), and if something has a certain value, that value has to be in the appropriate location memory; This means moves (which usually involve transferring data from one variable to another) have to actually perform a copy.

Related

Are return values shallow copies in Rust?

When returning a value from a function as such
let x = String::from("Hello");
let y = do_something(x);
with
fn do_something(s: String) -> String { s }
Does Rust do a shallow copy (ie copying the stack value of s into y), or does it do something else ? A shallow copy is made when passing, but is it the same behavior when returning it ?
A shallow copy is made when passing, but is it the same behavior when returning it?
Ignoring the first part (which is more complicated) and answering just what you want to know: yes, both parameter passing and returning have the same mechanism.
Now to the first part: that mechanism is called "moving", but it also matters whether we're talking about the language semantics (i.e. what the language spec describes as happening according to its abstract execution model) or what actually happens at runtime on a physical computer (which is allowed to be different, so long as the program still does the right thing according to the language spec).
In the language semantics, most types (i.e. those which do not implement Copy) are passed as parameters or returned by "moving", not "copying". The difference is that moving implies a change of ownership, whereas copying implies that the original owner retains ownership but the recipient receives ownership of a copy.
In actual reality, a "move" is likely to be implemented as a copy of the stack value (what you call a shallow copy). However, that is not always the case, as the compiler may optimise how it pleases. Particularly if a function is inlined, then there is no need to make any copies of parameters or a return value; the inlined function will be executed in the same stack frame, so the stack value need not be copied to a different frame. Note that Rust's ownership semantics are required for this optimisation to work: the inlined function might change the value it takes ownership of (if the parameter is declared mut), and that change would be visible to the outer function (since it's done in the same stack frame), if not for the fact that the outer function isn't allowed to use the value after giving up ownership of it.

When should I use a reference instead of transferring ownership?

From the Rust book's chapter on ownership, non-copyable values can be passed to functions by either transferring ownership or by using a mutable or immutable reference. When you transfer ownership of a value, it can't be used in the original function anymore: you must return it back if you want to. When you pass a reference, you borrow the value and can still use it.
I come from languages where values are immutable by default (Haskell, Idris and the like). As such, I'd probably never think about using references at all. Having the same value in two places looks dangerous (or, at least, awkward) to me. Since references are a feature, there must be a reason to use them.
Are there situations I should force myself to use references? What are those situations and why are they beneficial? Or are they just for convenience and defaulting to passing ownership is fine?
Mutable references in particular look very dangerous.
They are not dangerous, because the Rust compiler will not let you do anything dangerous. If you have a &mut reference to a value then you cannot simultaneously have any other references to it.
In general you should pass references around. This saves copying memory and should be the default thing you do, unless you have a good reason to do otherwise.
Some good reasons to transfer ownership instead:
When the value's type is small in size, such as bool, u32, etc. It's often better performance to move/copy these values to avoid a level of indirection. Usually these values implement Copy, and actually the compiler may make this optimisation for you automatically. Something it's free to do because of a strong type system and immutability by default!
When the value's current owner is going to go out of scope, you may want to move the value somewhere else to keep it alive.

Why does the Rust book present assigning a variable to another as copying the top-level structure?

In the section on ownership in The Rust Programming Language, Strings are represented as a structure with 3 fields (with one of the 3 fields being a pointer to the actual byte vector). There is an example:
let s1 = String::from("hello");
let s2 = s1;
The book explains this as copying the 3-field structure contained in s1 to s2 (but not the byte-vector) and then marking the structure contained in s1 as "invalid" (figure 4-4).
Why is it presented that way instead of presenting s2 as pointing to the same top-level structure as s1 and then marking s1 as "invalid"?
Would this alternate presentation result in a visible difference in semantics (or would it even cause problems)? If not, is it because it better reflects the underlying implementation? And if so, why would the implementation make such a copy operation?
Why is it presented that way
Because that's a very close (if not exact) way of modeling Rust's ownership and moving semantics.
Would this alternate presentation result in a visible difference in semantics
Yes. Rust's current semantics indicate that when a variable is moved, there's no guarantee that it remains at the same address. Your alternate presentation would suggest to readers that the address is guaranteed to be the same ("because the picture told me so!").
This cannot be the case for every move, so it's not worth teaching people misleading semantics. It's hard to pinpoint specifics, but cases I'd expect to have a higher chance of the value moving:
Transferring them across threads
Returning values from a function — although (Named) Return Value Optimization can prevent this.
When the value is "very small" — it's cheaper to copy it than to dereference memory.
why would the implementation make such a copy operation?
The implementation doesn't necessarily make a copy. While the semantics provide no guarantee that the address stays the same, they also don't enforce that it must change. In fact, the optimizer spends time attempting to minimize all sorts of needless copies where it can. The particular example in question is extremely likely to not involve any copies.

Is it always preferable to pass in a mutable reference vs creating and returning an owned value?

Coming to Rust from dynamic languages like Python, I'm not used to the programming pattern where you provide a function with a mutable reference to an empty data structure and that function populates it. A typical example is reading a file into a String:
let mut f = File::open("file.txt").unwrap();
let mut contents = String::new();
f.read_to_string(&mut contents).unwrap();
To my Python-accustomed eyes, an API where you just create an owned value within the function and move it out as a return value looks much more intuitive / ergonomic / what have you:
let mut f = File::open("file.txt").unwrap();
let contents = f.read_to_string().unwrap();
Since the Rust standard library takes the former road, I figure there must be a reason for that.
Is it always preferable to use the reference pattern? If so, why? (Performance reasons? What specifically?) If not, how do I spot the cases where it might be beneficial? Is it mostly useful when I want to return another value in addition to populating the result data structure (as in the first example above, where .read_to_string() returns the number of bytes read)? Why not use a tuple? Is it simply a matter of personal preference?
If read_to_string wanted to return an owned String, this means it would have to heap allocate a new String every time it was called. Also, because Read implementations don't always know how much data there is to be read, it would probably have to incrementally re-allocate the work-in-progress String multiple times. This also means every temporary String has to go back to the allocator to be destroyed.
This is wasteful. Rust is a system programming language. System programming languages abhor waste.
Instead, the caller is responsible for allocating and providing the buffer. If you only call read_to_string once, nothing changes. If you call it more than once, however, you can re-use the same buffer multiple times without the constant allocate/resize/deallocate cycle. Although it doesn't apply in this specific case, similar interfaces can be design to also support stack buffers, meaning in some cases you can avoid heap activity entirely.
Having the caller pass the buffer in is strictly more flexible than the alternative.

Box<X> vs move semantics on X

I have an easy question regarding Box<X>.
I understand what it does, it allocates X on the heap.
In C++ you use the new operator to allocate something on the heap so it can outlive the current scope (because if you create something on the stack it goes away at the end of the current block).
But reading Rust's documentation, it looks like you can create something on the stack and still return it taking advantage of the language's move semantics without having to resort to the heap.
Then it's not clear to me when to use Box<X> as opposed to simply X.
I just started reading about Rust so I apologize if I'm missing something obvious.
First of all: C++11 (and newer) has move semantics with rvalue references, too. So your question would also apply to C++. Keep in mind though, that C++'s move semantics are -- unlike Rust's ones -- highly unsafe.
Second: the word "move semantic" somehow hints the absence of a "copy", which is not true. Suppose you have a struct with 100 64-bit integers. If you would transfer an object of this struct via move semantics, those 100 integers will be copied (of course, the compiler's optimizer can often remove those copies, but anyway...). The advantage of move semantics comes to play when dealing with objects that deal with some kind of data on the heap (or pointers in general).
For example, take a look at Vec (similar to C++'s vector): the type itself only contains a pointer and two pointer-sized integer (ptr, len and cap). Those three times 64bit are still copied when the vector is moved, but the main data of the vector (which lives on the heap) is not touched.
That being said, let's discuss the main question: "Why to use Box at all?". There are actually many use cases:
Unsized types: some types (e.g. Trait-objects which also includes closures) are unsized, meaning their size is not known to the compiler. But the compiler has to know the size of each stack frame -- hence those unsized types cannot live on the stack.
Recursive data structures: think of a BinaryTreeNode struct. It saves two members named "left" and "right" of type... BinaryTreeNode? That won't work. So you can box both children so that the compiler knows the size of your struct.
Huge structs: think of the 100 integer struct mentioned above. If you don't want to copy it every time, you can allocate it on the heap (this happens pretty seldom).
There are cases where you can’t return X eg. if X is ?Sized (traits, non-compile-time-sized arrays, etc.). In those cases Box<X> will still work.

Resources