Step 6 of 6
Ownership and collections
A Vec owns its elements. Pushing a String moves it into the vector, and when the vector is dropped, it drops every element first. Ownership forms a tree: main owns roster, roster owns its strings, and each string owns its text.
STACK HEAP: roster's buffer HEAP: each string's text
roster ┌──────────────────────┐
┌────────────┐ │ [0] ptr ─────────────┼─────────► "Ana"
│ ptr ───────┼───► │ len 3 cap 3 │
│ len 2 │ ├──────────────────────┤
│ cap 4 │ │ [1] ptr ─────────────┼─────────► "Bartholomew"
└────────────┘ │ len 11 cap 11 │
└──────────────────────┘
When roster goes out of scope, the whole tree is freed, each block exactly once, without a line of code from you.
fn main() {
let mut roster: Vec<String> = Vec::new();
let name = String::from("Ana");
roster.push(name); // name moves into the Vec
roster.push(String::from("Bartholomew"));
println!("{} players: {:?}", roster.len(), roster);
}
2 players: ["Ana", "Bartholomew"]
Vec::new() creates an empty vector, like String::new() does for text. The annotation Vec<String> says what it will hold (Rust could also work that out from the first push).
You can't move out of an index
Taking an element by index is fine for Copy types: let first = scores[0]; copies the number. With Strings it doesn't compile:
fn main() {
let names = vec![String::from("Ana"), String::from("Ben")];
let first = names[0];
println!("{} {}", first, names.len());
}
error[E0507]: cannot move out of index of `Vec<String>`
--> src/main.rs:3:17
|
3 | let first = names[0];
| ^^^^^^^^ move occurs because value has type `String`, which does not implement the `Copy` trait
|
help: consider borrowing here
|
3 | let first = &names[0];
| +
help: consider cloning the value if the performance cost is acceptable
|
3 | let first = names[0].clone();
| ++++++++
Why? Moving names[0] out would leave a hole: the vector would still believe it owns a string in slot 0 and would free it again when it's dropped. Rust refuses. The compiler's two suggestions are the real options:
names[0].clone(): make your own copy; the vector keeps its string.&names[0]: borrow it without copying (next module).
And if you're done with the whole vector, a for loop moves every element out of it, one by one. That's allowed because the vector is used up as a whole:
fn main() {
let names = vec![String::from("Ana"), String::from("Ben")];
let captain = names[0].clone(); // our own copy
for name in names { // moves each String out; names is gone after
println!("player: {}", name);
}
println!("captain: {}", captain);
}
player: Ana
player: Ben
captain: Ana
Move, copy or clone?
| Situation | What to do |
|---|---|
The value is a small Copy type (numbers, bool, char) |
Nothing: it's copied automatically |
| You're done with the value and something else should own it now | Move it |
| You need two independent values that will change separately | Clone |
| Something only needs to read the value for a moment | Borrow it (next module) |
Why this is worth it
Look at what these rules rule out, with no garbage collector and no run-time checks:
- Double free: a value has one owner, so its memory is freed once.
- Use after free: memory is freed only when its owner's scope ends, and using a moved-from variable doesn't compile.
- Forgotten frees: every value is dropped when its owner goes out of scope, so you can't leak memory by forgetting a
free. - Data races (two threads changing the same data at once): the one-owner idea, extended by the borrowing rules of the next module, stops them at compile time. You'll see it in the threads module.
C++ has the same idea in std::unique_ptr and move semantics, but its compiler lets you keep using a moved-from object. Rust checks it.
Common mistakes
let x = v[i];with aVec<String>(E0507): clone it, or borrow it.- Using a
Stringafter pushing it into a vector (E0382): use it before the push, or reach it through the vector afterwards (println!("{}", v[0])only borrows it, which is fine). - Looping with
for x in vand then usingv(E0382).
Your task
The program reads player names (one per line), adds each to a roster, and names the first player captain. It has two ownership errors. Fix the first by reordering (print the name before it moves), and the second with one .clone(). For the input Ana, Bartholomew, Cy (one per line) it prints:
added Ana
added Bartholomew
added Cy
captain: Ana
- Ana
- Bartholomew
- Cy
Challenge 2
The program finds the most expensive product. prices holds u32s (Copy), so prices[best] is fine, but products[best] tries to move a String out of the vector. Fix that line with .clone() (and don't clone the price: it's Copy). The program should print:
most expensive: monitor at $189
in stock: keyboard
in stock: monitor
in stock: mouse
Challenge 3
Write build_roster. It takes ownership of the captain and of a vector of other players, and returns a new vector with the captain first, followed by the others in order. Everything can be moved, so don't use .clone().
For example, build_roster(String::from("Ana"), vec![String::from("Ben"), String::from("Cy")]) returns ["Ana", "Ben", "Cy"].