RustArena

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:

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:

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

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"].

Previous: Ownership and functions