How it works
The explanation, with examples you can run.
Moves
fn shout(s: String) -> String { s.to_uppercase() // takes ownership of s; s is dropped at the end} fn main() { let a = String::from("hi"); let b = a; // move: b owns the heap buffer now, a is unusable // println!("{a}"); // error[E0382]: borrow of moved value: `a` let c = b.clone(); // deep copy: a second heap buffer let loud = shout(b); // b moves into the function println!("{c} {loud}"); let x = 5; let y = x; // i32 is Copy: both stay usable println!("{x} {y}");}It prints hi HI and 5 5. A String is a small struct on the stack (pointer, length, capacity) pointing at bytes on the heap. let b = a copies that struct and makes a invalid, so only one variable will ever free the buffer. Uncomment the println! and the compiler says:
error[E0382]: borrow of moved value: `a` |2 | let a = String::from("hi"); | - move occurs because `a` has type `String`, which does not implement the `Copy` trait3 | let b = a; | - value moved herePassing a value to a function moves it too, as shout(b) does, and returning a value moves it out to the caller. Moving is cheap: it never copies the heap data.
Copy and Clone
Types whose bits are the whole value, such as integers, floats, bool, char, shared references, and tuples or arrays of them, implement Copy. For them, assignment copies and the original stays usable. A type can be Copy only if it has no Drop code and nothing it owns needs freeing, which is why String isn't.
Clone is the explicit, possibly expensive duplicate. #[derive(Clone)] writes it for your own types. If you see .clone() sprinkled to silence errors, it usually means a borrow would have done.
Drop is deterministic
Values are dropped at the end of the scope that owns them, in reverse order of declaration. Anything can hook into that by implementing Drop: files close, locks unlock and connections return to their pool exactly when their owner goes away, even when a function returns early with ?. This is the same idea as C++'s RAII, enforced by the compiler.
Ownership doesn't mean everything is on the heap. A struct of numbers lives wherever its owner does, often on the stack, and moving it is a plain copy of its bytes. The difference from other languages is that the compiler tracks who is responsible for it.
Passing ownership everywhere would be clumsy, so the next topic, borrowing, lets code use a value without taking it.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.