Guide contents

Lesson 18 of 94

Ownership & Borrowing with Collections

Most ownership and borrowing errors in real programs happen around collections. A Vec or HashMap owns many values, hands out references into its own storage, and can move that storage when it grows. This lesson applies the rules from the previous lessons to that situation, one realistic mistake at a time.

Every failing example is a real compiler error, shown with its output, with the fix next to it. Three ideas explain almost all of them:

  • An element is not yours to move. You can borrow it, clone it, or remove it from the collection. You cannot take it and leave a hole.
  • A reference into a collection freezes the collection. While you hold &v[0] or map.get(k), nothing may add or remove elements, because that could move the storage.
  • A borrow lasts until its last use. Most fixes are a matter of ordering: finish reading, then write.

1. Getting an element out of a Vec

Indexing looks like it hands you the element, but for a type that is not Copy it would have to move the element out and leave a hole in the vector. Rust refuses:

Rust✗ does not compile · E0507
fn main() {
    let names = vec![String::from("Ana"), String::from("Ben")];
    let first = names[0];
    println!("{first} and {} names", names.len());
}
Output
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();
  |                         ++++++++

names[0] is a place inside the vector, not a value you own. You have three choices: borrow it, clone it, or take it out of the vector for real.

Borrow it with &, or clone it when you need your own copy:

Rust
fn main() {
    let names = vec![String::from("Ana"), String::from("Ben")];
    let borrowed = &names[0];
    let owned = names[1].clone();
    println!("{borrowed} and {owned}, still {} names", names.len());
}
Output
Ana and Ben, still 2 names

2. Taking an element out for real

To move an element out of a Vec, use a method that removes it, so the vector never has a hole. remove shifts the rest left, swap_remove is O(1) but moves the last element into the gap, and pop takes the last one:

Rust
fn main() {
    let mut queue = vec![
        String::from("a"),
        String::from("b"),
        String::from("c"),
        String::from("d"),
    ];
    let first = queue.remove(0);
    let swapped = queue.swap_remove(0);
    let last = queue.pop();
    println!("{first} {swapped} {last:?} left: {queue:?}");
}
Output
a b Some("c") left: ["d"]

3. Taking a value out of a slot with mem::take

Sometimes you cannot remove the element, because other code relies on the index staying valid. std::mem::take moves the value out and leaves Default::default() in its place; mem::replace leaves a value you choose:

Rust
use std::mem;

fn main() {
    let mut slots = vec![String::from("draft"), String::from("final")];
    let old = mem::take(&mut slots[0]);
    let older = mem::replace(&mut slots[1], String::from("archived"));
    println!("{old:?} {older:?} -> {slots:?}");
}
Output
"draft" "final" -> ["", "archived"]

4. for x in v moves the vector

A plain for loop calls into_iter(), which consumes the vector. The elements are moved out one by one, so the vector cannot be used afterwards:

Rust✗ does not compile · E0382
fn main() {
    let names = vec![String::from("Ana"), String::from("Ben")];
    for name in names {
        println!("hello {name}");
    }
    println!("{} names", names.len());
}
Output
error[E0382]: borrow of moved value: `names`
 --> src/main.rs:6:26
  |
2 |     let names = vec![String::from("Ana"), String::from("Ben")];
  |         ----- move occurs because `names` has type `Vec<String>`, which does not implement the `Copy` trait
3 |     for name in names {
  |                 ----- `names` moved due to this implicit call to `.into_iter()`
...
6 |     println!("{} names", names.len());
  |                          ^^^^^ value borrowed here after move
  |
note: `into_iter` takes ownership of the receiver `self`, which moves `names`
 --> /rustc/e408947bfd200af42db322daf0fadfe7e26d3bd1/library/core/src/iter/traits/collect.rs:310:17
help: consider iterating over a slice of the `Vec<String>`'s content to avoid moving into the `for` loop
  |
3 |     for name in &names {
  |                 +

Loop over &names to borrow (name is a &String), over &mut names to modify in place, or over names when you want to consume it:

The three ways to loop:

Rust
fn main() {
    let mut names = vec![String::from("Ana"), String::from("Ben")];

    for name in &names {
        println!("read {name}");
    }
    for name in &mut names {
        name.push('!');
    }
    for name in names {
        println!("consumed {name}");
    }
}
Output
read Ana
read Ben
consumed Ana!
consumed Ben!

5. A reference into a Vec blocks push

This is the collection bug behind most borrow checker errors. first points into the vector's buffer. push may reallocate that buffer and leave first dangling, so the compiler rejects the push while first is still in use:

Rust✗ does not compile · E0502
fn main() {
    let mut scores = vec![10, 20, 30];
    let first = &scores[0];
    scores.push(40);
    println!("first = {first}");
}
Output
error[E0502]: cannot borrow `scores` as mutable because it is also borrowed as immutable
 --> src/main.rs:4:5
  |
3 |     let first = &scores[0];
  |                  ------ immutable borrow occurs here
4 |     scores.push(40);
  |     ^^^^^^^^^^^^^^^ mutable borrow occurs here
5 |     println!("first = {first}");
  |                        ----- immutable borrow later used here

The borrow lasts from where it is created to its last use, not to the end of the block. Reorder so the last use comes first, or copy the value out:

Two fixes: use the reference before the push, or copy the number out (an i32 is Copy):

Rust
fn main() {
    let mut scores = vec![10, 20, 30];

    let first = &scores[0];
    println!("first = {first}");
    scores.push(40);

    let copied = scores[0];
    scores.push(50);
    println!("copied = {copied}, scores = {scores:?}");
}
Output
first = 10
copied = 10, scores = [10, 20, 30, 40, 50]

6. Returning a reference to an element

A lookup function borrows the collection and returns a reference tied to it. The lifetime 'a says: the result is valid only as long as the slice is borrowed.

Rust
struct User {
    name: String,
    age: u32,
}

fn find_user<'a>(users: &'a [User], name: &str) -> Option<&'a User> {
    users.iter().find(|u| u.name == name)
}

fn main() {
    let mut users = vec![
        User { name: String::from("ana"), age: 31 },
        User { name: String::from("ben"), age: 27 },
    ];

    if let Some(user) = find_user(&users, "ben") {
        println!("found {} ({})", user.name, user.age);
    }

    users.push(User { name: String::from("cy"), age: 45 });
    println!("{} users", users.len());
}
Output
found ben (27)
3 users

The if let block ends the borrow, so the later push is fine. If you kept user alive past the push, you would get the same E0502 as in the previous example.

7. You cannot return a reference to a local collection

The other direction of the same rule: a reference cannot outlive the thing it points to. A vector created inside a function is dropped when the function returns:

Rust✗ does not compile · E0515
fn longest_word() -> &'static str {
    let words = vec![String::from("ownership"), String::from("borrowing")];
    &words[0]
}

fn main() {
    println!("{}", longest_word());
}
Output
error[E0515]: cannot return value referencing local variable `words`
 --> src/main.rs:3:5
  |
3 |     &words[0]
  |     ^-----^^^
  |     ||
  |     |`words` is borrowed here
  |     returns a value referencing data owned by the current function

Return the owned value (words.remove(0) or .clone()), or take the collection as a parameter so the caller keeps ownership.

8. HashMap::insert moves the key and the value

The map takes ownership of whatever you insert. Keys and values that are not Copy are gone from the caller:

Rust✗ does not compile · E0382
use std::collections::HashMap;

fn main() {
    let mut stock = HashMap::new();
    let item = String::from("apple");
    stock.insert(item, 5);
    println!("added {item}");
}
Output
error[E0382]: borrow of moved value: `item`
 --> src/main.rs:7:22
  |
5 |     let item = String::from("apple");
  |         ---- move occurs because `item` has type `String`, which does not implement the `Copy` trait
6 |     stock.insert(item, 5);
  |                  ---- value moved here
7 |     println!("added {item}");
  |                      ^^^^ value borrowed here after move
  |
  = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider cloning the value if the performance cost is acceptable
  |
6 |     stock.insert(item.clone(), 5);
  |                      ++++++++

Insert a clone, or insert a &str that lives long enough. Look-ups take a borrowed form of the key, so stock.get("apple") works with a &str even though the key is a String:

Rust
use std::collections::HashMap;

fn main() {
    let mut stock = HashMap::new();
    let item = String::from("apple");
    stock.insert(item.clone(), 5);
    println!("added {item}, stock of apple: {:?}", stock.get("apple"));
}
Output
added apple, stock of apple: Some(5)

9. get borrows, get_mut borrows mutably

get returns Option<&V> and get_mut returns Option<&mut V>. Both keep the map borrowed for as long as you use the result:

Rust
use std::collections::HashMap;

fn main() {
    let mut stock: HashMap<String, u32> = HashMap::new();
    stock.insert(String::from("apple"), 5);

    if let Some(count) = stock.get_mut("apple") {
        *count += 10;
    }
    match stock.get("apple") {
        Some(count) => println!("apple: {count}"),
        None => println!("no apples"),
    }
}
Output
apple: 15

10. A reference from a map blocks insert

The same rule as with Vec. An entry reference points into the map's storage, and insert may rehash and move everything:

Rust✗ does not compile · E0502
use std::collections::HashMap;

fn main() {
    let mut stock: HashMap<String, u32> = HashMap::new();
    stock.insert(String::from("apple"), 5);

    let apples = stock.get("apple");
    stock.insert(String::from("pear"), 2);
    println!("{apples:?}");
}
Output
error[E0502]: cannot borrow `stock` as mutable because it is also borrowed as immutable
 --> src/main.rs:8:5
  |
7 |     let apples = stock.get("apple");
  |                  ----- immutable borrow occurs here
8 |     stock.insert(String::from("pear"), 2);
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
9 |     println!("{apples:?}");
  |                ------ immutable borrow later used here

Read what you need first (let apples = stock.get("apple").copied(); ends the borrow immediately), or restructure so the mutation comes after the last use.

11. The entry API: look up and insert in one borrow

The classic "if the key is missing, insert; then update it" needs two accesses, and two accesses mean two borrows. entry does it in one and gives you a &mut V:

Rust
use std::collections::HashMap;

fn main() {
    let text = "the cat and the hat and the bat";
    let mut counts: HashMap<&str, u32> = HashMap::new();
    for word in text.split_whitespace() {
        *counts.entry(word).or_insert(0) += 1;
    }

    let mut pairs: Vec<_> = counts.iter().collect();
    pairs.sort();
    println!("{pairs:?}");
}
Output
[("and", 2), ("bat", 1), ("cat", 1), ("hat", 1), ("the", 3)]

12. Grouping values into a HashMap<K, Vec<V>>

or_default() inserts an empty Vec the first time, and returns &mut Vec. Each push moves an owned value into the group:

Rust
use std::collections::HashMap;

fn main() {
    let words = vec!["apple", "avocado", "banana", "blueberry", "cherry"];
    let mut by_letter: HashMap<char, Vec<String>> = HashMap::new();

    for word in words {
        let letter = word.chars().next().unwrap();
        by_letter.entry(letter).or_default().push(word.to_string());
    }

    let mut letters: Vec<_> = by_letter.keys().copied().collect();
    letters.sort();
    for letter in letters {
        println!("{letter}: {:?}", by_letter[&letter]);
    }
}
Output
a: ["apple", "avocado"]
b: ["banana", "blueberry"]
c: ["cherry"]

13. Reading one entry while writing another

Two different keys in the same map still count as one borrow of the whole map. You cannot hold a reference to one entry and call get_mut for another:

Rust✗ does not compile · E0502
use std::collections::HashMap;

fn main() {
    let mut teams: HashMap<String, Vec<String>> = HashMap::new();
    teams.insert(String::from("red"), vec![String::from("ana")]);
    teams.insert(String::from("blue"), vec![String::from("ben")]);

    let red = &teams["red"];
    teams.get_mut("blue").unwrap().extend(red.iter().cloned());
    println!("{teams:?}");
}
Output
error[E0502]: cannot borrow `teams` as mutable because it is also borrowed as immutable
 --> src/main.rs:9:5
  |
8 |     let red = &teams["red"];
  |                ----- immutable borrow occurs here
9 |     teams.get_mut("blue").unwrap().extend(red.iter().cloned());
  |     ^^^^^^^^^^^^^^^^^^^^^                 --- immutable borrow later used here
  |     |
  |     mutable borrow occurs here

There are two practical fixes: clone what you need before writing, or take the value out of the map, use it, and put it back. (A third is to keep the two lists in different collections.) Cloning is the honest answer when the data is small:

Clone first so the read borrow ends before the write:

Rust
use std::collections::HashMap;

fn main() {
    let mut teams: HashMap<String, Vec<String>> = HashMap::new();
    teams.insert(String::from("red"), vec![String::from("ana")]);
    teams.insert(String::from("blue"), vec![String::from("ben")]);

    let red_members = teams["red"].clone();
    teams.get_mut("blue").unwrap().extend(red_members);

    println!("blue = {:?}", teams["blue"]);
}
Output
blue = ["ben", "ana"]

Or move the value out, work on both, and put it back. remove gives you ownership, so no borrow is left:

Rust
use std::collections::HashMap;

fn main() {
    let mut teams: HashMap<String, Vec<String>> = HashMap::new();
    teams.insert(String::from("red"), vec![String::from("ana")]);
    teams.insert(String::from("blue"), vec![String::from("ben")]);

    let red = teams.remove("red").unwrap();
    teams.get_mut("blue").unwrap().extend(red.iter().cloned());
    teams.insert(String::from("red"), red);

    println!("blue = {:?}, red = {:?}", teams["blue"], teams["red"]);
}
Output
blue = ["ben", "ana"], red = ["ana"]

14. Two rows of a Vec<Vec<T>> at once

Indexing with &mut grid[i] borrows the whole outer vector, so a second &mut grid[j] conflicts, even when i != j. The compiler cannot know the indices differ:

Rust✗ does not compile · E0502
fn main() {
    let mut grid = vec![vec![1, 2, 3], vec![0, 0, 0]];
    let src = &grid[0];
    let dst = &mut grid[1];
    dst.copy_from_slice(src);
    println!("{grid:?}");
}
Output
error[E0502]: cannot borrow `grid` as mutable because it is also borrowed as immutable
 --> src/main.rs:4:20
  |
3 |     let src = &grid[0];
  |                ---- immutable borrow occurs here
4 |     let dst = &mut grid[1];
  |                    ^^^^ mutable borrow occurs here
5 |     dst.copy_from_slice(src);
  |                         --- immutable borrow later used here
  |
  = help: use `.split_at_mut(position)` to obtain two mutable non-overlapping sub-slices

split_at_mut hands you two non-overlapping halves, and the compiler can see they are disjoint:

Rust
fn main() {
    let mut grid = vec![vec![1, 2, 3], vec![0, 0, 0]];
    let (top, bottom) = grid.split_at_mut(1);
    bottom[0].copy_from_slice(&top[0]);
    println!("{grid:?}");
}
Output
[[1, 2, 3], [1, 2, 3]]

15. Swapping and moving between rows

For swapping two elements, Vec::swap takes indices, so no references are held. To move a row out and back, mem::take leaves an empty Vec behind:

Rust
use std::mem;

fn main() {
    let mut grid = vec![vec![1, 2], vec![3, 4], vec![5, 6]];

    grid.swap(0, 2);

    let row = mem::take(&mut grid[1]);
    grid[0].extend(row);

    println!("{grid:?}");
}
Output
[[5, 6, 3, 4], [], [1, 2]]

16. A method that borrows all of self while you loop over a field

Inside a method, self.items and self.total are separate places, and Rust can borrow them separately. But a call to self.discount() borrows all of self, which conflicts with the loop:

Rust✗ does not compile · E0502
struct Cart {
    items: Vec<u32>,
    total: u32,
}

impl Cart {
    fn discount(&self) -> u32 {
        self.items.len() as u32
    }

    fn checkout(&mut self) {
        for price in &mut self.items {
            *price -= self.discount();
            self.total += *price;
        }
    }
}

fn main() {
    let mut cart = Cart { items: vec![10, 20], total: 0 };
    cart.checkout();
    println!("{}", cart.total);
}
Output
error[E0502]: cannot borrow `*self` as immutable because it is also borrowed as mutable
  --> src/main.rs:13:23
   |
12 |         for price in &mut self.items {
   |                      ---------------
   |                      |
   |                      mutable borrow occurs here
   |                      mutable borrow later used here
13 |             *price -= self.discount();
   |                       ^^^^ immutable borrow occurs here

Compute what the loop needs before it starts, or make the helper a plain function that takes only the data it needs:

Compute the value first. Now the loop borrows only self.items and self.total, which are disjoint fields:

Rust
struct Cart {
    items: Vec<u32>,
    total: u32,
}

impl Cart {
    fn discount(&self) -> u32 {
        self.items.len() as u32
    }

    fn checkout(&mut self) {
        let discount = self.discount();
        for price in &mut self.items {
            *price -= discount;
            self.total += *price;
        }
    }
}

fn main() {
    let mut cart = Cart { items: vec![10, 20], total: 0 };
    cart.checkout();
    println!("{}", cart.total);
}
Output
26

17. Emptying a collection inside a struct: take, drain, split_off

To hand out the contents of a field while leaving the struct usable, you cannot move the field (the struct would be half-empty). Use mem::take, drain(..), or split_off:

Rust
use std::mem;

struct Outbox {
    pending: Vec<String>,
}

impl Outbox {
    fn flush_all(&mut self) -> Vec<String> {
        mem::take(&mut self.pending)
    }

    fn flush_first(&mut self, n: usize) -> Vec<String> {
        self.pending.drain(..n).collect()
    }

    fn flush_tail(&mut self, from: usize) -> Vec<String> {
        self.pending.split_off(from)
    }
}

fn main() {
    let mut outbox = Outbox {
        pending: ["a", "b", "c", "d", "e"].iter().map(|s| s.to_string()).collect(),
    };
    println!("{:?}", outbox.flush_first(2));
    println!("{:?}", outbox.flush_tail(2));
    println!("{:?}", outbox.flush_all());
    println!("{:?}", outbox.pending);
}
Output
["a", "b"]
["e"]
["c", "d"]
[]

18. Removing elements while you scan: retain and retain_mut

You cannot remove from a Vec inside a for x in &v loop, because the loop holds a borrow. retain runs your closure on each element and keeps the ones for which it returns true. retain_mut gives the closure a &mut T, so it can also modify what it keeps:

Rust
struct Task {
    title: String,
    done: bool,
    priority: u8,
}

fn main() {
    let mut tasks = vec![
        Task { title: String::from("write"), done: true, priority: 1 },
        Task { title: String::from("test"), done: false, priority: 2 },
        Task { title: String::from("ship"), done: false, priority: 3 },
    ];

    tasks.retain_mut(|t| {
        t.priority += 1;
        !t.done
    });
    for t in &tasks {
        println!("{} (priority {})", t.title, t.priority);
    }
}
Output
test (priority 3)
ship (priority 4)

19. Moving items between collections

remove gives you ownership, so you can push the item into another collection. append moves every element of one Vec into another and leaves the source empty; extend(other.drain(..)) does the same with a range:

Rust
fn main() {
    let mut todo = vec![String::from("write"), String::from("test"), String::from("ship")];
    let mut done: Vec<String> = Vec::new();

    let finished = todo.remove(0);
    done.push(finished);

    let mut later = vec![String::from("celebrate")];
    todo.append(&mut later);

    println!("todo = {todo:?}, done = {done:?}, later = {later:?}");
}
Output
todo = ["test", "ship", "celebrate"], done = ["write"], later = []

20. Collecting references: Vec<&T> and Vec<&mut T>

A Vec<&T> is a view into another collection. It stores no data of its own, so it is cheap, but it borrows the original for as long as the view is used. iter_mut().collect() gives a Vec<&mut T> that lets you edit selected elements:

Rust
#[derive(Debug)]
struct Player {
    name: String,
    score: u32,
}

fn main() {
    let mut players = vec![
        Player { name: String::from("ana"), score: 80 },
        Player { name: String::from("ben"), score: 45 },
        Player { name: String::from("cy"), score: 92 },
    ];

    let leaders: Vec<&Player> = players.iter().filter(|p| p.score >= 80).collect();
    let names: Vec<&str> = leaders.iter().map(|p| p.name.as_str()).collect();
    println!("leaders: {names:?}");

    let mut low: Vec<&mut Player> = players.iter_mut().filter(|p| p.score < 50).collect();
    for p in low.iter_mut() {
        p.score += 10;
    }
    println!("{players:?}");
}
Output
leaders: ["ana", "cy"]
[Player { name: "ana", score: 80 }, Player { name: "ben", score: 55 }, Player { name: "cy", score: 92 }]

leaders is last used on the line that builds names, so players is free to be borrowed mutably afterwards.

21. Finding an element to modify

iter_mut().find(...) returns Option<&mut T>. It is the standard way to update one element of a collection that you locate by a property rather than an index:

Rust
struct Item {
    sku: String,
    qty: u32,
}

fn restock(items: &mut [Item], sku: &str, amount: u32) -> bool {
    match items.iter_mut().find(|i| i.sku == sku) {
        Some(item) => {
            item.qty += amount;
            true
        }
        None => false,
    }
}

fn main() {
    let mut items = vec![
        Item { sku: String::from("A1"), qty: 3 },
        Item { sku: String::from("B2"), qty: 0 },
    ];
    println!("{} {}", restock(&mut items, "B2", 12), restock(&mut items, "Z9", 1));
    println!("B2 qty = {}", items[1].qty);
}
Output
true false
B2 qty = 12

22. A closure that captures a collection

A closure that modifies a collection holds a &mut borrow for as long as the closure is still used. Reading the collection in between is a conflict:

Rust✗ does not compile · E0502
fn main() {
    let mut log: Vec<String> = Vec::new();
    let mut record = |msg: &str| log.push(msg.to_string());

    record("start");
    println!("entries so far: {}", log.len());
    record("finish");
}
Output
error[E0502]: cannot borrow `log` as immutable because it is also borrowed as mutable
 --> src/main.rs:6:36
  |
3 |     let mut record = |msg: &str| log.push(msg.to_string());
  |                      ----------- --- first borrow occurs due to use of `log` in closure
  |                      |
  |                      mutable borrow occurs here
...
6 |     println!("entries so far: {}", log.len());
  |                                    ^^^ immutable borrow occurs here
7 |     record("finish");
  |     ------ mutable borrow later used here

Finish with the closure before you read the collection, or pass the collection into the function as a parameter instead of capturing it:

Rust
fn record(log: &mut Vec<String>, msg: &str) {
    log.push(msg.to_string());
}

fn main() {
    let mut log: Vec<String> = Vec::new();
    record(&mut log, "start");
    println!("entries so far: {}", log.len());
    record(&mut log, "finish");
    println!("{log:?}");
}
Output
entries so far: 1
["start", "finish"]

23. Returning an iterator that borrows a collection

A method can return an iterator over its data without copying it. The + '_ says the iterator borrows from &self; the items are references:

Rust
struct Roster {
    people: Vec<(String, u32)>,
}

impl Roster {
    fn adults(&self) -> impl Iterator<Item = &str> + '_ {
        self.people
            .iter()
            .filter(|(_, age)| *age >= 18)
            .map(|(name, _)| name.as_str())
    }
}

fn main() {
    let roster = Roster {
        people: vec![
            (String::from("ana"), 31),
            (String::from("kid"), 9),
            (String::from("ben"), 27),
        ],
    };
    let names: Vec<&str> = roster.adults().collect();
    println!("{names:?}");
}
Output
["ana", "ben"]

24. Storing references to elements: a collection that borrows another

A HashMap<&str, &User> is a fast index over a Vec<User> without copying any user. It is valid only while the Vec is not modified or moved:

Rust
struct User {
    name: String,
    email: String,
}

fn main() {
    let users = vec![
        User { name: String::from("ana"), email: String::from("ana@example.com") },
        User { name: String::from("ben"), email: String::from("ben@example.com") },
    ];

    let by_name: std::collections::HashMap<&str, &User> =
        users.iter().map(|u| (u.name.as_str(), u)).collect();

    println!("{}", by_name["ben"].email);
}
Output
ben@example.com

Now try to add a user while the index is alive. The index points into the vector's buffer, so push is rejected:

Rust✗ does not compile · E0502
use std::collections::HashMap;

struct User {
    name: String,
    email: String,
}

fn main() {
    let mut users = vec![User { name: String::from("ana"), email: String::from("a@x.com") }];
    let by_name: HashMap<&str, &User> = users.iter().map(|u| (u.name.as_str(), u)).collect();

    users.push(User { name: String::from("ben"), email: String::from("b@x.com") });
    println!("{}", by_name["ana"].email);
}
Output
error[E0502]: cannot borrow `users` as mutable because it is also borrowed as immutable
  --> src/main.rs:12:5
   |
10 |     let by_name: HashMap<&str, &User> = users.iter().map(|u| (u.name.as_str(), u)).collect();
   |                                         ----- immutable borrow occurs here
11 |
12 |     users.push(User { name: String::from("ben"), email: String::from("b@x.com") });
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
13 |     println!("{}", by_name["ana"].email);
   |                    ------- immutable borrow later used here

A borrowing index is a snapshot: build it, use it, drop it. If the data keeps changing, store indices or ids instead of references, as in the next example.

25. Indices instead of references: an arena

When items refer to each other, and the collection keeps growing, store usize indices into one Vec. An index is Copy, holds no borrow, and stays valid when you push. This is the standard way to build trees and graphs in Rust:

Rust
struct Node {
    name: String,
    parent: Option<usize>,
    children: Vec<usize>,
}

struct OrgChart {
    nodes: Vec<Node>,
}

impl OrgChart {
    fn add(&mut self, name: &str, parent: Option<usize>) -> usize {
        let id = self.nodes.len();
        self.nodes.push(Node { name: name.to_string(), parent, children: Vec::new() });
        if let Some(p) = parent {
            self.nodes[p].children.push(id);
        }
        id
    }

    fn path_to_root(&self, mut id: usize) -> Vec<&str> {
        let mut path = vec![self.nodes[id].name.as_str()];
        while let Some(p) = self.nodes[id].parent {
            path.push(self.nodes[p].name.as_str());
            id = p;
        }
        path
    }
}

fn main() {
    let mut chart = OrgChart { nodes: Vec::new() };
    let ceo = chart.add("Ada", None);
    let cto = chart.add("Grace", Some(ceo));
    let dev = chart.add("Linus", Some(cto));
    chart.add("Ken", Some(cto));

    println!("{:?}", chart.path_to_root(dev));
    println!("Grace manages {} people", chart.nodes[cto].children.len());
}
Output
["Linus", "Grace", "Ada"]
Grace manages 2 people

26. Sharing one value between collections: Rc

Sometimes two collections genuinely need to own the same value. Cloning duplicates the data; a reference ties one collection's lifetime to the other. A shared-ownership pointer, Rc<T>, fixes it: cloning an Rc copies only a pointer and a counter. See Rc & Weak for details.

Rust
use std::rc::Rc;

fn main() {
    let ana = Rc::new(String::from("Ana Lovelace"));

    let admins = vec![Rc::clone(&ana)];
    let authors = vec![Rc::clone(&ana), Rc::new(String::from("Ben"))];

    println!("{} / {}", admins[0], authors[0]);
    println!("owners of Ana: {}", Rc::strong_count(&ana));
}
Output
Ana Lovelace / Ana Lovelace
owners of Ana: 3

27. Realistic example: deduplicating while keeping order

A HashSet<&str> can borrow from the owned Strings in a Vec, so checking for duplicates copies nothing. The set is dropped before the vector is used again:

Rust
use std::collections::HashSet;

fn dedupe(words: Vec<String>) -> Vec<String> {
    let mut seen: HashSet<String> = HashSet::new();
    let mut unique = Vec::new();
    for word in words {
        if seen.insert(word.clone()) {
            unique.push(word);
        }
    }
    unique
}

fn dedupe_borrowed(words: &[String]) -> Vec<&str> {
    let mut seen: HashSet<&str> = HashSet::new();
    words
        .iter()
        .map(|w| w.as_str())
        .filter(|w| seen.insert(*w))
        .collect()
}

fn main() {
    let words: Vec<String> = "b a b c a d".split(' ').map(String::from).collect();
    println!("{:?}", dedupe_borrowed(&words));
    println!("{:?}", dedupe(words));
}
Output
["b", "a", "c", "d"]
["b", "a", "c", "d"]

dedupe takes ownership and needs a clone to keep one copy in the set. dedupe_borrowed borrows the words and returns &str views, so it allocates nothing but the result vector. Which one is right depends on whether the caller still needs the original.

28. Realistic example: an inventory that processes orders

This pulls the pieces together: a map of owned items, orders consumed by value, mutable lookups with get_mut, and error values that borrow nothing from the map:

Rust
use std::collections::HashMap;

struct Order {
    sku: String,
    qty: u32,
}

enum OrderError {
    UnknownSku(String),
    OutOfStock { sku: String, wanted: u32, have: u32 },
}

impl OrderError {
    fn describe(&self) -> String {
        match self {
            OrderError::UnknownSku(sku) => format!("unknown sku {sku}"),
            OrderError::OutOfStock { sku, wanted, have } => {
                format!("{sku}: wanted {wanted}, have {have}")
            }
        }
    }
}

struct Inventory {
    stock: HashMap<String, u32>,
}

impl Inventory {
    fn fulfil(&mut self, order: Order) -> Result<u32, OrderError> {
        let have = self
            .stock
            .get_mut(&order.sku)
            .ok_or_else(|| OrderError::UnknownSku(order.sku.clone()))?;
        if *have < order.qty {
            return Err(OrderError::OutOfStock {
                sku: order.sku,
                wanted: order.qty,
                have: *have,
            });
        }
        *have -= order.qty;
        Ok(*have)
    }
}

fn main() {
    let mut inv = Inventory {
        stock: HashMap::from([(String::from("A1"), 5), (String::from("B2"), 1)]),
    };
    let orders = vec![
        Order { sku: String::from("A1"), qty: 3 },
        Order { sku: String::from("B2"), qty: 2 },
        Order { sku: String::from("Z9"), qty: 1 },
        Order { sku: String::from("A1"), qty: 2 },
    ];

    for order in orders {
        match inv.fulfil(order) {
            Ok(left) => println!("ok, {left} left"),
            Err(e) => println!("failed: {}", e.describe()),
        }
    }
}
Output
ok, 2 left
failed: B2: wanted 2, have 1
failed: unknown sku Z9
ok, 0 left

29. Realistic example: top scorer per group

Group owned records into a map, then consume each group with into_iter to move the winner out. Nothing is cloned, and nothing borrows from the map after the loop body ends:

Rust
use std::collections::HashMap;

#[derive(Debug)]
struct Employee {
    name: String,
    team: String,
    sales: u32,
}

fn top_per_team(staff: Vec<Employee>) -> Vec<Employee> {
    let mut groups: HashMap<String, Vec<Employee>> = HashMap::new();
    for e in staff {
        groups.entry(e.team.clone()).or_default().push(e);
    }

    let mut winners: Vec<Employee> = groups
        .into_values()
        .filter_map(|members| members.into_iter().max_by_key(|e| e.sales))
        .collect();
    winners.sort_by(|a, b| a.team.cmp(&b.team));
    winners
}

fn main() {
    let staff = vec![
        Employee { name: String::from("ana"), team: String::from("east"), sales: 40 },
        Employee { name: String::from("ben"), team: String::from("west"), sales: 55 },
        Employee { name: String::from("cy"), team: String::from("east"), sales: 62 },
        Employee { name: String::from("di"), team: String::from("west"), sales: 12 },
    ];
    for w in top_per_team(staff) {
        println!("{}: {} ({})", w.team, w.name, w.sales);
    }
}
Output
east: cy (62)
west: ben (55)

30. Realistic example: a grid step with a double buffer

To update every cell from its neighbours, you cannot read and write the same grid. Build the next state in a second grid, then swap. mem::swap exchanges the two vectors in O(1) without copying any cell:

Rust
use std::mem;

fn step(current: &Vec<Vec<u8>>, next: &mut Vec<Vec<u8>>) {
    let rows = current.len();
    let cols = current[0].len();
    for r in 0..rows {
        for c in 0..cols {
            let left = if c > 0 { current[r][c - 1] } else { 0 };
            let right = if c + 1 < cols { current[r][c + 1] } else { 0 };
            next[r][c] = left + right;
        }
    }
}

fn main() {
    let mut current = vec![vec![0, 1, 0, 0], vec![1, 0, 0, 1]];
    let mut next = vec![vec![0; 4]; 2];

    for _ in 0..2 {
        step(&current, &mut next);
        mem::swap(&mut current, &mut next);
        println!("{current:?}");
    }
}
Output
[[1, 0, 1, 0], [0, 1, 1, 0]]
[[0, 2, 0, 1], [1, 1, 1, 1]]

Cheat sheet: collection borrow errors

You wrote Error Why Fix
let x = v[0]; (non-Copy) E0507 Cannot move out of an index &v[0], v[0].clone(), v.remove(0), mem::take(&mut v[0])
for x in v { ... } then use v E0382 The loop consumed v for x in &v or for x in &mut v
let r = &v[0]; v.push(..); use(r) E0502 push may move the buffer Use r first, copy or clone the value, or store an index
map.insert(k, v) then use k E0382 The map took ownership k.clone(), or use &str keys
let a = map.get(k); map.insert(..); use(a) E0502 An entry reference freezes the map .copied() / .cloned() first, or reorder
if !map.contains_key(k) { insert } then update none (works, but slower) Two accesses per key *map.entry(k).or_insert(0) += 1
&mut grid[i] and &grid[j] together E0502 The whole Vec is borrowed split_at_mut, swap, mem::take, clone one row
self.helper() inside for x in &mut self.items E0502 The method borrows all of self Compute first, or use a function of the fields
Remove inside for x in &v E0502 The loop holds a borrow retain, retain_mut, drain, or collect indices first
Closure captures &mut vec, then read vec E0502 The closure still holds the borrow Use the closure first, or pass &mut vec as an argument
Vec<&T> (or HashMap<&str, &T>) then push to the source E0502 The view points into the source Build the view after mutating, or store indices or ids
return &local_vec[0] E0515 The vector is dropped at the end of the function Return the owned value

Summary

  • You cannot move an element out of a Vec with indexing. Borrow it, clone it, or remove it with remove, swap_remove, pop, mem::take, or mem::replace.
  • for x in v consumes v. Use &v to read and &mut v to modify.
  • A reference into a Vec or HashMap blocks every operation that could resize it (push, insert, remove) until its last use.
  • insert moves the key and value into the map. get and get_mut return references. entry does look-up-or-insert in one borrow.
  • Two entries of the same collection are one borrow. Use split_at_mut, swap, cloning, or remove and re-insert.
  • In a method, separate fields can be borrowed separately, but a method call borrows all of self.
  • retain, retain_mut, drain, split_off, and mem::take change collections without a manual loop and without leaving holes.
  • Vec<&T> and HashMap<&str, &T> are cheap views that freeze their source. When the data keeps changing, store indices or ids, or share with Rc.

This completes the Ownership & Borrowing section. Next, you will use ownership to build your own types: Structs.