However, a few types, including integers, but also things like booleans and chars, implement a trait (which for the purposes of this discussion is like an interface, if you're not familiar with traits) called Copy that means that they should be implicitly copied rather than moved. This means that in the specific example you gave above, there would not be any error, since `x` would be copied implicitly. You can also implement Copy on your own types, but this is generally only supposed to be done on things that are relatively small due to the performance overheard of large copies. Instead, for larger types, you can implement Clone, which gives a `.clone` method that lets you explicitly copy the type while still having moves rather than copies be the default. Notably, the Copy trait can only be implemented on types that already implement Clone, so anything that is implicitly copied be can also be explicitly copied as well
Clone would be what comes closest to copy constructors, and it has to be explicitly invoked.
This rule really helps both the compiler and the programmer to make moving and copying things effortless. It does prevent things like a struct holding internal pointers to it's own state. This is not bad on x86, because you can replace internal pointers with internal offsets, and the instruction set contains a fast reg+reg addressing mode, but can cost an extra instruction on many other cpu architectures. IMHO the rule is well worth it, although it is an example of a situation where Rust chooses to give away a bit of performance for sanity.
There is no "read the docs and don't screw up" because the program will not compile at all if you do it wrong, and the compiler will explain why.
It's very useful for data ownership e.g. move file or string, you can forget about its existence (you have to really). It's also extremely useful to encode "static" state machines, especially when they're attached to some sort of unique resource: on state transition, consume the old state (one type) and return the new state (a different type). Not all state machines can be encoded thus and still useful, but when they do it's really nice.
It is a compiler error to use the value after it was moved. And the compiler error is quite explicit about how to solve it
For example you can explicitly clone
let y = add(x.clone());
let z = add(x);What the chance in real life that a function like that cannot simply use a reference?
In my learning Rust, my life became significantly better and easier when I started to references to borrow as much as possible.
Not rethorical, I'm genuinely curious as I used to struggle with the copy vs move decisions, and a bunch of issues with ownership which went all away when I started using pointer/borrowing everywhere.
You'd have to write two extra lines of code to set up y and z separately from calling add(), and you'd have to make them mutable which is an extra mental burden.
And an immutable reference is bad because it would force extra objects to be allocated, wasting time and cache space.
It's a very much a "bondage and discipline"-style language in the sense that unless you explicitly use "unsafe", you have to prove to the compiler that everything you do is safe. Moving large things is not unsafe because after you move something to a different scope, the original doesn't exist anymore and can't be used, so if you were to accidentally move something and tried to use it again, the compiler would helpfully tell you that the thing you're trying to refer to isn't there anymore.
There is no warning on every time you pass something without copy because passing things to different scopes is extremely common, normal and desired.
Compare with C++, in particular types with deleted copy operators (e.g. unique_ptr<T>). In order to call a function that takes an unique_ptr by value as argument, you must explicitly move the object into the function:
void foo(unique_ptr x) {
...
}
unique_ptr x = ...;
foo(move(x));
foo(move(x));
Linters (i.e. clang-tidy) can be configured to complain about this, but it's completely valid C++ (because move leaves the object in an unspecified but valid state). In Rust, the argument will be automatically moved in the first call, and the second call will generate a compile-time error.Also and importantly because move() only indicates that the value may be moved from (it's a fancy cast), that doesn't mean it will be moved from. The called function needs to take an rvalue reference (T&&) for a move to possibly happen. That is if you had a
void foo(unique_ptr&& x)
it may or may not move. Likewise you can call move() on things which are not move constructible (or passing the result to functions which don't care) with no ill effects (save to the reader).IIRC what happens here is that since foo() takes a unique_ptr by value the compiler will insert the construction of a temporary unique_ptr and overload resolution will select the move constructor (since we've provided an rvalue reference, and assuming the wrapped type is move-constructible).
> move leaves the object in an unspecified but valid state
Also notes that this is the general semantics, but specific types can define their exact behaviour here. In particular, unique_ptr specifies that a moved-from unique_ptr is "nulled" (equal to nullptr).
(I realize you, saagarjha, probably know this already -- this is more a note to other readers.)
fn add1(x: Thing) -> Thing {
x + 1
}
fn add2(x: &Thing) -> Thing {
x + 2
}
This works on the assumption that whatever "+ 1" really means doesn't itself need ownership of x, e.g. because it mutates it. If it does, then you'll get an error. Or you could do this: fn add3(x: &mut Thing) {
x.add(3)
}
Which, presumably, modifies x instead of returning a copy. It's your job to make sure it makes sense to do that, but Rust has another trick in its pockets: let y = add1(x); // Works; takes ownership.
let z = add2(&y); // Works, and doesn't take ownership.
let zz = add2(&y); // So you can do it twice. (Y tho?)
let h = add3(&y); // Compile-time error!
let hh = add3(&mut y); // Works. You need to specify that you're fine with y being mutated. fn add1(x: Vec<i32>) -> Vec<i32> { // Takes ownership
return x.iter().map(|&x| x + 1).collect::<Vec<_>>();
}
fn add1_borrow(x: &Vec<i32>) -> Vec<i32> {
return x.iter().map(|&x| x + 1).collect::<Vec<_>>();
}
The first will take ownership of the thing you pass in, so you can't do something like this: let x = vec![1, 2, 3];
let y = add1(x);
let z = add1(x); // error[E0382]: use of moved value: `x`
but the follow code is OK, because the function just borrows the value instead of owning (and destroying) it: let x = vec![1, 2, 3];
let y = add1_borrow(&x);
let z = add1_borrow(&x);
This is legal too: let x = vec![1, 2, 3];
let y = add1_borrow(&x);
let z = add1(x);
but as before you can't use x after this.(Note, I chose Vec here because Vec does not implement Copy: if I used i32 it would just copy the value in the non-borrowing case, which would make the code fine as no ownership would be transferred.)
If I do the rust equivalent of:
then will x have been deallocated by the first call to add1 and will the second call to add1 fail?[You can ignore the fact that I'm using numbers and substitute an object if that makes more sense in the context of allocating / deallocating memory in rust.]