One value, one owner
Ownership is the central idea of Rust, and it replaces both manual memory management and a garbage collector. The rules are short:
- Every value has exactly one owner, the variable responsible for it.
- When the owner goes out of scope, the value is cleaned up automatically.
- Assigning a value that owns memory to another variable moves it. The new variable becomes the owner, and the old one can no longer be used.
The compiler checks these rules before the program runs. If the code breaks one, it doesn't compile.
Cleanup happens at the closing brace
The cleanup in the second rule is exact. When a variable goes out of scope, the value it owns is dropped at that point. A small type that prints a message when dropped makes this visible. Declare one value in an inner block and another in the outer function, and the inner one is dropped at the closing brace of its block, while the outer one is dropped when the function ends. Because there is only ever one owner, a value is cleaned up exactly once, never twice and never forgotten.
Moving a String
let s1 = String::from("AlgoCademy");
let s2 = s1;
println!("s2 = {}", s2);
After let s2 = s1; the text exists once in memory, and s2 owns it. Nothing was duplicated, so a move is cheap: only the small handle that points at the text is transferred. The print shows s2 = AlgoCademy.
Using a variable after it moved
Now try to use the old owner:
let s1 = String::from("AlgoCademy");
let s2 = s1;
println!("s1 = {}", s1);
The program is rejected with error[E0382]: borrow of moved value: `s1`. Read the message from the notes up. The first one says move occurs because `s1` has type `String`, which does not implement the `Copy` trait. The second marks value moved here at let s2 = s1;. The last marks value borrowed here after move at the print. The help line suggests s1.clone().
Moves also happen when you pass a String to a function by value. After show(s) with fn show(s: String), the variable s in the caller is moved and the same error appears if it is used again. A Vec moves in the same way. Passing a reference instead, show(&s) with fn show(s: &String), only lends the value, and the caller keeps ownership.
Clone: a real copy
If you need two independent values, ask for a duplicate:
let s1 = String::from("AlgoCademy");
let s2 = s1;
let s3 = s2.clone();
println!("s2 = {}, s3 = {}", s2, s3);
clone() allocates new memory and copies the text, so s2 and s3 each own their own data and both stay usable. It costs time and memory proportional to the size of the data, which is why Rust makes you write it out instead of doing it silently.
Copy types are the exception
Small values of fixed size, such as i32, f64, bool and char, implement the Copy trait. Assigning one just copies its bits, and the original stays valid:
let n1 = 7;
let n2 = n1;
println!("n1 = {}, n2 = {}", n1, n2);
This prints n1 = 7, n2 = 7. A string literal such as "hi" is a &str, a shared reference, which is Copy too, and a tuple of Copy values is Copy. The rule of thumb: types that own heap memory, like String and Vec, move; simple values copy.
Solving the exercise
The exercise walks through the three behaviors in turn, printing after each one:
- Create the owner.
let s1 = String::from("AlgoCademy");makes an ownedString, which is the kind of value that moves. - Move it.
let s2 = s1;is a plain assignment with noclone. From here,s1is off limits. - Print the new owner.
println!("s2 = {}", s2);. Printings1instead would not compile. - Clone.
let s3 = s2.clone();duplicates the data, ands2stays alive. - Print both in one
println!, with two{}placeholders filled in order. - Create and copy an integer.
let n1 = 7;andlet n2 = n1;need no clone, becausei32isCopy. - Print both numbers, which works because
n1was never invalidated.
let s1 = String::from("AlgoCademy");
let s2 = s1;
println!("s2 = {}", s2);
let s3 = s2.clone();
println!("s2 = {}, s3 = {}", s2, s3);
let n1 = 7;
let n2 = n1;
println!("n1 = {}, n2 = {}", n1, n2);
The output is:
s2 = AlgoCademy
s2 = AlgoCademy, s3 = AlgoCademy
n1 = 7, n2 = 7
Common mistakes
- Using the old variable after a move. Ask who owns the value right now. If the answer isn't the variable you just wrote, that is the error.
- Reaching for
clone()every time. It silences the error, but it also copies the data. Often a reference (&) is the better fix, because it lends the value without copying. - Expecting every type to move. Integers, floats, booleans and chars copy, so the same assignment that breaks for a
Stringworks for them.
Key takeaways
- Each value has one owner, and it is cleaned up when the owner goes out of scope.
- Assigning a
String(or passing it to a function by value) moves it, and the old variable becomes unusable. clone()makes a real duplicate, so both variables stay valid.Copytypes likei32are copied on assignment and never move.
