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]ormap.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:
fn main() {
let names = vec![String::from("Ana"), String::from("Ben")];
let first = names[0];
println!("{first} and {} names", 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();
| ++++++++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:
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());
}Ana and Ben, still 2 names2. 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:
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:?}");
}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:
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:?}");
}"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:
fn main() {
let names = vec![String::from("Ana"), String::from("Ben")];
for name in names {
println!("hello {name}");
}
println!("{} names", names.len());
}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:
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}");
}
}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:
fn main() {
let mut scores = vec![10, 20, 30];
let first = &scores[0];
scores.push(40);
println!("first = {first}");
}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 hereThe 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):
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:?}");
}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.
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());
}found ben (27)
3 usersThe 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:
fn longest_word() -> &'static str {
let words = vec![String::from("ownership"), String::from("borrowing")];
&words[0]
}
fn main() {
println!("{}", longest_word());
}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 functionReturn 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:
use std::collections::HashMap;
fn main() {
let mut stock = HashMap::new();
let item = String::from("apple");
stock.insert(item, 5);
println!("added {item}");
}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:
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"));
}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:
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"),
}
}apple: 1510. 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:
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:?}");
}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 hereRead 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:
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:?}");
}[("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:
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]);
}
}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:
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:?}");
}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 hereThere 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:
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"]);
}blue = ["ben", "ana"]Or move the value out, work on both, and put it back. remove gives you ownership, so no borrow is left:
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"]);
}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:
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:?}");
}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-slicessplit_at_mut hands you two non-overlapping halves, and the compiler can see they are disjoint:
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:?}");
}[[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:
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:?}");
}[[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:
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);
}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 hereCompute 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:
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);
}2617. 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:
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);
}["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:
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);
}
}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:
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:?}");
}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:
#[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:?}");
}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:
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);
}true false
B2 qty = 1222. 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:
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");
}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 hereFinish with the closure before you read the collection, or pass the collection into the function as a parameter instead of capturing it:
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:?}");
}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:
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:?}");
}["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:
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);
}ben@example.comNow try to add a user while the index is alive. The index points into the vector's buffer, so push is rejected:
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);
}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 hereA 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:
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());
}["Linus", "Grace", "Ada"]
Grace manages 2 people26. 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.
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));
}Ana Lovelace / Ana Lovelace
owners of Ana: 327. 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:
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));
}["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:
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()),
}
}
}ok, 2 left
failed: B2: wanted 2, have 1
failed: unknown sku Z9
ok, 0 left29. 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:
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);
}
}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:
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(¤t, &mut next);
mem::swap(&mut current, &mut next);
println!("{current:?}");
}
}[[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
Vecwith indexing. Borrow it, clone it, or remove it withremove,swap_remove,pop,mem::take, ormem::replace. for x in vconsumesv. Use&vto read and&mut vto modify.- A reference into a
VecorHashMapblocks every operation that could resize it (push,insert,remove) until its last use. insertmoves the key and value into the map.getandget_mutreturn references.entrydoes look-up-or-insert in one borrow.- Two entries of the same collection are one borrow. Use
split_at_mut,swap, cloning, orremoveand 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, andmem::takechange collections without a manual loop and without leaving holes.Vec<&T>andHashMap<&str, &T>are cheap views that freeze their source. When the data keeps changing, store indices or ids, or share withRc.
This completes the Ownership & Borrowing section. Next, you will use ownership to build your own types: Structs.
