Working with the Borrow Checker
Smart pointers · Lesson 40
The borrow checker is the part of the compiler that enforces ownership, borrowing and mutable borrows. When it says no, the program is usually one small change away from compiling: borrow instead of move, copy a small value out first, split a struct into parts, take a value out with take, or keep an index instead of a reference. Each fix has a cost, and some errors point at a design that should change.
Coming from GoGo lets any pointer write and keeps whatever is reachable alive. The habits that meet a Rust error are a pointer into a slice that append then moves, two slices sharing one array, a pointer to the range variable (before Go 1.22), and a method that changes a struct while a loop reads another part of it.
Coming from KotlinTwo names can reach one object and either can change it. List is a read-only view, and MutableList or a cast gets the writes back. Rust puts "read" or "change" in the signature (& or &mut) and refuses a second writer.
Coming from Pythonb = a makes a second name for one list, and either name changes it. That is documented behaviour, not a bug. In Rust two names that both change one list are E0499: one owner changes it and the other borrows it for a call, or you clone.
Coming from TypeScriptconst b = a is a second name for the same array, and either can push. In Rust two names that both change one array are E0499: one owner changes it and the other borrows it for a call, or you clone.
Coming from JavaTwo names can reach one object and any of them can change it. A getter that returns the internal List lets the caller change it unless you wrap it (Collections.unmodifiableList). Rust's &[T] getter is read-only, and objects that point at each other become ids, or Rc with Weak.
Coming from C#A property that returns the internal List<T> lets the caller change it, unless it returns an IReadOnlyList<T>. Rust's &[T] getter is read-only, and a separate &mut self method says that the caller may change.
Coming from CA realloc that moves an array leaves every pointer into it dangling. GCC's -Wall warns about a direct use after realloc, and says nothing when the realloc is inside a push helper. Rust refuses the push while a reference into the array is alive.
Coming from C++A moved-from std::string or std::vector is valid but in an unspecified state (libstdc++ leaves it empty), so v[0] on a moved-from vector crashes. A reference into a vector survives a push_back until it does not. Rust refuses both at compile time: a moved value cannot be used, and a push cannot happen while a reference is alive.
The quick comparison
struct Item { name: String, count: u32,}
impl Item { fn new(name: &str, count: u32) -> Item { Item { name: name.to_string(), count, } }}
struct Inventory { items: Vec<Item>, log: Vec<String>,}
impl Inventory { fn check(&mut self) { for item in &self.items { if item.count == 0 { // `items` and `log` are two fields: reading one while writing the other is fine self.log.push(format!("out of stock: {}", item.name)); } } }}
fn main() { let mut inv = Inventory { items: vec![ Item::new("bolt", 0), Item::new("nut", 12), Item::new("washer", 0), ], log: Vec::new(), }; inv.check(); println!("log: {:?}", inv.log);}log: ["out of stock: bolt", "out of stock: washer"]
package main
import ( "fmt" "strings")
type Item struct { name string count int}
type Inventory struct { items []Item log []string}
func (inv *Inventory) check() { for _, item := range inv.items { if item.count == 0 { inv.log = append(inv.log, "out of stock: "+item.name) } }}
func show(xs []string) string { quoted := make([]string, len(xs)) for i, x := range xs { quoted[i] = fmt.Sprintf("%q", x) } return "[" + strings.Join(quoted, ", ") + "]"}
func main() { inv := &Inventory{items: []Item{{"bolt", 0}, {"nut", 12}, {"washer", 0}}} inv.check() fmt.Println("log:", show(inv.log))}log: ["out of stock: bolt", "out of stock: washer"]
class Item(val name: String, val count: Int)
class Inventory(val items: List<Item>) { val log = mutableListOf<String>()
fun check() { for (item in items) { if (item.count == 0) { log.add("out of stock: ${item.name}") } } }}
fun main() { val inv = Inventory(listOf(Item("bolt", 0), Item("nut", 12), Item("washer", 0))) inv.check() println("log: [" + inv.log.joinToString(", ") { "\"$it\"" } + "]")}log: ["out of stock: bolt", "out of stock: washer"]
class Item: def __init__(self, name, count): self.name = name self.count = count
class Inventory: def __init__(self, items): self.items = items self.log = []
def check(self): for item in self.items: if item.count == 0: self.log.append("out of stock: " + item.name)
inv = Inventory([Item("bolt", 0), Item("nut", 12), Item("washer", 0)])inv.check()print("log: [" + ", ".join('"' + line + '"' for line in inv.log) + "]")log: ["out of stock: bolt", "out of stock: washer"]
class Item { constructor( readonly name: string, readonly count: number, ) {}}
class Inventory { log: string[] = [];
constructor(readonly items: Item[]) {}
check(): void { for (const item of this.items) { if (item.count === 0) { this.log.push("out of stock: " + item.name); } } }}
const inv = new Inventory([new Item("bolt", 0), new Item("nut", 12), new Item("washer", 0)]);inv.check();console.log("log: [" + inv.log.map((line) => '"' + line + '"').join(", ") + "]");log: ["out of stock: bolt", "out of stock: washer"]
import java.util.ArrayList;import java.util.List;import java.util.stream.Collectors;
class Item { final String name; final int count;
Item(String name, int count) { this.name = name; this.count = count; }}
class Inventory { final List<Item> items; final List<String> log = new ArrayList<>();
Inventory(List<Item> items) { this.items = items; }
void check() { for (Item item : items) { if (item.count == 0) { log.add("out of stock: " + item.name); } } }}
public class Main { public static void main(String[] args) { Inventory inv = new Inventory(List.of(new Item("bolt", 0), new Item("nut", 12), new Item("washer", 0))); inv.check(); System.out.println("log: " + inv.log.stream().map(s -> "\"" + s + "\"").collect(Collectors.joining(", ", "[", "]"))); }}log: ["out of stock: bolt", "out of stock: washer"]
var inv = new Inventory([new Item("bolt", 0), new Item("nut", 12), new Item("washer", 0)]);inv.Check();Console.WriteLine("log: [" + string.Join(", ", inv.Log.Select(s => $"\"{s}\"")) + "]");
class Item(string name, int count){ public string Name { get; } = name; public int Count { get; } = count;}
class Inventory(List<Item> items){ public List<string> Log { get; } = new();
public void Check() { foreach (var item in items) { if (item.Count == 0) { Log.Add($"out of stock: {item.Name}"); } } }}log: ["out of stock: bolt", "out of stock: washer"]
#include <stdio.h>
struct Item { const char *name; int count;};
struct Inventory { struct Item items[3]; const char *log[3]; int log_len;};
static void check(struct Inventory *inv) { for (int i = 0; i < 3; i++) { if (inv->items[i].count == 0) { inv->log[inv->log_len++] = inv->items[i].name; } }}
int main(void) { struct Inventory inv = {{{"bolt", 0}, {"nut", 12}, {"washer", 0}}, {0}, 0}; check(&inv); printf("log: ["); for (int i = 0; i < inv.log_len; i++) { printf("%s\"out of stock: %s\"", i ? ", " : "", inv.log[i]); } printf("]\n");}log: ["out of stock: bolt", "out of stock: washer"]
#include <iostream>#include <string>#include <vector>
struct Item { std::string name; int count;};
struct Inventory { std::vector<Item> items; std::vector<std::string> log;
void check() { for (const Item &item : items) { if (item.count == 0) { log.push_back("out of stock: " + item.name); } } }};
int main() { Inventory inv{{{"bolt", 0}, {"nut", 12}, {"washer", 0}}, {}}; inv.check(); std::cout << "log: ["; for (size_t i = 0; i < inv.log.size(); i++) { std::cout << (i ? ", " : "") << '"' << inv.log[i] << '"'; } std::cout << "]\n";}log: ["out of stock: bolt", "out of stock: washer"]
What's different
- Read a borrow error as three places: where the value was moved or borrowed, the use that clashes, and a later use that keeps the first alive. A fix moves one of the three.
self.note(..)takes&mut self, which borrows all ofself. A field access such asself.log.push(..)names one field, and the compiler can see that two fields are separate places. Either use the field, work out what to do first and then call the method, or split the struct.cloneis a fix that compiles and costs a copy. It is right for a small value that you need twice, and wrong when two parts of the program must see the same changes.- When an error keeps coming back, change the design: one owner per piece of state, indices or keys instead of references between values, and "decide with
&self, then apply with&mut self".
A method call while a field is being read
Looks fine: in Go, Kotlin, Python, TypeScript, Java, C#, C and C++ it runs fine. Rust won't compile it.
Fine in Go. Rust can't prove it's safe.Fine in Kotlin. Rust can't prove it's safe.Fine in Python. Rust can't prove it's safe.Fine in TypeScript. Rust can't prove it's safe.Fine in Java. Rust can't prove it's safe.Fine in C#. Rust can't prove it's safe.Fine in C. Rust can't prove it's safe.Fine in C++. Rust can't prove it's safe.struct Item { name: String, count: u32,}
impl Item { fn new(name: &str, count: u32) -> Item { Item { name: name.to_string(), count, } }}
struct Inventory { items: Vec<Item>, log: Vec<String>,}
impl Inventory { fn note(&mut self, text: &str) { self.log.push(text.to_string()); }
fn check(&mut self) { for item in &self.items { if item.count == 0 { self.note(&format!("out of stock: {}", item.name)); } } }}
fn main() { let mut inv = Inventory { items: vec![ Item::new("bolt", 0), Item::new("nut", 12), Item::new("washer", 0), ], log: Vec::new(), }; inv.check(); println!("log: {:?}", inv.log);}package main
import ( "fmt" "strings")
type Item struct { name string count int}
type Inventory struct { items []Item log []string}
func (inv *Inventory) note(text string) { inv.log = append(inv.log, text)}
func (inv *Inventory) check() { for _, item := range inv.items { if item.count == 0 { inv.note("out of stock: " + item.name) } }}
func show(xs []string) string { quoted := make([]string, len(xs)) for i, x := range xs { quoted[i] = fmt.Sprintf("%q", x) } return "[" + strings.Join(quoted, ", ") + "]"}
func main() { inv := &Inventory{items: []Item{{"bolt", 0}, {"nut", 12}, {"washer", 0}}} inv.check() fmt.Println("log:", show(inv.log))}log: ["out of stock: bolt", "out of stock: washer"]
class Item(val name: String, val count: Int)
class Inventory(val items: List<Item>) { val log = mutableListOf<String>()
fun note(text: String) { log.add(text) }
fun check() { for (item in items) { if (item.count == 0) { note("out of stock: ${item.name}") } } }}
fun main() { val inv = Inventory(listOf(Item("bolt", 0), Item("nut", 12), Item("washer", 0))) inv.check() println("log: [" + inv.log.joinToString(", ") { "\"$it\"" } + "]")}log: ["out of stock: bolt", "out of stock: washer"]
class Item: def __init__(self, name, count): self.name = name self.count = count
class Inventory: def __init__(self, items): self.items = items self.log = []
def note(self, text): self.log.append(text)
def check(self): for item in self.items: if item.count == 0: self.note("out of stock: " + item.name)
inv = Inventory([Item("bolt", 0), Item("nut", 12), Item("washer", 0)])inv.check()print("log: [" + ", ".join('"' + line + '"' for line in inv.log) + "]")log: ["out of stock: bolt", "out of stock: washer"]
class Item { constructor( readonly name: string, readonly count: number, ) {}}
class Inventory { log: string[] = [];
constructor(readonly items: Item[]) {}
note(text: string): void { this.log.push(text); }
check(): void { for (const item of this.items) { if (item.count === 0) { this.note("out of stock: " + item.name); } } }}
const inv = new Inventory([new Item("bolt", 0), new Item("nut", 12), new Item("washer", 0)]);inv.check();console.log("log: [" + inv.log.map((line) => '"' + line + '"').join(", ") + "]");log: ["out of stock: bolt", "out of stock: washer"]
import java.util.ArrayList;import java.util.List;import java.util.stream.Collectors;
class Item { final String name; final int count;
Item(String name, int count) { this.name = name; this.count = count; }}
class Inventory { final List<Item> items; final List<String> log = new ArrayList<>();
Inventory(List<Item> items) { this.items = items; }
void note(String text) { log.add(text); }
void check() { for (Item item : items) { if (item.count == 0) { note("out of stock: " + item.name); } } }}
public class Main { public static void main(String[] args) { Inventory inv = new Inventory(List.of(new Item("bolt", 0), new Item("nut", 12), new Item("washer", 0))); inv.check(); System.out.println("log: " + inv.log.stream().map(s -> "\"" + s + "\"").collect(Collectors.joining(", ", "[", "]"))); }}log: ["out of stock: bolt", "out of stock: washer"]
var inv = new Inventory([new Item("bolt", 0), new Item("nut", 12), new Item("washer", 0)]);inv.Check();Console.WriteLine("log: [" + string.Join(", ", inv.Log.Select(s => $"\"{s}\"")) + "]");
class Item(string name, int count){ public string Name { get; } = name; public int Count { get; } = count;}
class Inventory(List<Item> items){ public List<string> Log { get; } = new();
public void Note(string text) { Log.Add(text); }
public void Check() { foreach (var item in items) { if (item.Count == 0) { Note($"out of stock: {item.Name}"); } } }}log: ["out of stock: bolt", "out of stock: washer"]
#include <stdio.h>
struct Item { const char *name; int count;};
struct Inventory { struct Item items[3]; const char *log[3]; int log_len;};
static void note(struct Inventory *inv, const char *name) { inv->log[inv->log_len++] = name;}
static void check(struct Inventory *inv) { for (int i = 0; i < 3; i++) { if (inv->items[i].count == 0) { note(inv, inv->items[i].name); } }}
int main(void) { struct Inventory inv = {{{"bolt", 0}, {"nut", 12}, {"washer", 0}}, {0}, 0}; check(&inv); printf("log: ["); for (int i = 0; i < inv.log_len; i++) { printf("%s\"out of stock: %s\"", i ? ", " : "", inv.log[i]); } printf("]\n");}log: ["out of stock: bolt", "out of stock: washer"]
#include <iostream>#include <string>#include <vector>
struct Item { std::string name; int count;};
struct Inventory { std::vector<Item> items; std::vector<std::string> log;
void note(const std::string &text) { log.push_back(text); }
void check() { for (const Item &item : items) { if (item.count == 0) { note("out of stock: " + item.name); } } }};
int main() { Inventory inv{{{"bolt", 0}, {"nut", 12}, {"washer", 0}}, {}}; inv.check(); std::cout << "log: ["; for (size_t i = 0; i < inv.log.size(); i++) { std::cout << (i ? ", " : "") << '"' << inv.log[i] << '"'; } std::cout << "]\n";}log: ["out of stock: bolt", "out of stock: washer"]
What does the compiler say? Guess first, then open.
error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable --> src/main.rs:28:17 | 26 | for item in &self.items { | - ----------- | | | | | immutable borrow occurs here | _________| immutable borrow later used here | | 27 | | if item.count == 0 { 28 | | self.note(&format!("out of stock: {}", item.name)); | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here 29 | | } 30 | | } | |_________- this for loop borrows `*self` immutably, preventing mutation within its body | = help: consider using an index-based loop instead, or collecting modifications into a separate collection
Go runs this and prints the log. range reads the slice once at the start, and note appends to a different slice.
Kotlin runs this and prints the log. note changes log, not the list the loop walks, so there is no ConcurrentModificationException.
Python prints the log. note changes self.log, not the list being looped over.
Prints the log. note pushes to log, not to the array the loop walks.
Java runs this and prints the log. note adds to log; the list being iterated is not changed, so no ConcurrentModificationException.
C# runs this and prints the log. foreach walks items; Note changes Log, a different list.
C runs this and prints the log. note writes into the log array of the same struct; the loop reads items. Nothing checks that note stays away from items.
C++ runs this and prints the log. note pushes to log. If it pushed to items the push could reallocate the vector under the loop's iterators, and nothing would say so.
E0502: cannot borrow *self as mutable because it is also borrowed as immutable. The loop borrows self.items. self.note(..) takes &mut self, which borrows the whole of self, and items is part of it. The compiler checks the call with the signature alone, so it must assume note could change items while the loop reads it.
This run is fine because note only touches log. The compiler reasons from the signature, not the body, and a note that also pushed to items would change a list while a loop walks it. That is a real bug, and the languages differ in what it does: an exception in Java, Kotlin and C#; in Go the loop never reaches the new items; in Python and TypeScript it visits them, so the loop may never end (removing an item instead makes it skip the next one); undefined behaviour in C and C++. Mutable borrows has the runs. Name the field (self.log.push(..)), work out what to log first and call the method afterwards (as fixed.rs does), or split the struct so the log is its own type.
The fix
Compiles and runs.
struct Item { name: String, count: u32,}
impl Item { fn new(name: &str, count: u32) -> Item { Item { name: name.to_string(), count, } }}
struct Inventory { items: Vec<Item>, log: Vec<String>,}
impl Inventory { fn note(&mut self, text: &str) { self.log.push(text.to_string()); }
fn check(&mut self) { // work out what to log while `items` is borrowed, then call the method let mut out = Vec::new(); for item in &self.items { if item.count == 0 { out.push(format!("out of stock: {}", item.name)); } } for line in out { self.note(&line); } }}
fn main() { let mut inv = Inventory { items: vec![ Item::new("bolt", 0), Item::new("nut", 12), Item::new("washer", 0), ], log: Vec::new(), }; inv.check(); println!("log: {:?}", inv.log);}log: ["out of stock: bolt", "out of stock: washer"]
The toolkit
From error to fix
This page is for the moment after the borrow checker said no: what do I change? It does not re-teach the rules (Ownership, Borrowing, Mutable borrows).
A borrow error points at up to three places: the first move or borrow, the use that clashes, and a later use of the first that keeps it alive (Mutable borrows shows all three). A fix moves one: end the first borrow earlier, avoid the clash, or stop needing the first. Reading compiler errors explains the layout.
The help: lines are hints, not a diagnosis: a suggested clone can compile and do the wrong thing (moving out), and a suggested type can only move the error (the Java, Kotlin and C# box below).
A small fix, or a design problem? Most errors take one local change, listed below. These signs mean the design is the cause; read the redesign section:
- the third
cloneorRcin one struct, added to quiet an error; &mut selfmethods that call each other while a field is borrowed;- a struct that stores a reference into something it also owns, such as a parser holding a
Stringand slices of it (self-referential); - lifetime annotations spreading through three signatures;
- two parts that both want to change one value.
| Error | The compiler says | Try first |
|---|---|---|
| E0382 | borrow of moved value: x |
borrow instead of moving |
| E0499 | cannot borrow x as mutable more than once |
copy a value out, or split the data |
| E0502 | cannot borrow x as mutable because it is also borrowed as immutable |
end the first borrow earlier |
| E0500 | closure requires unique access to *self |
name the field inside the closure |
| E0505 | cannot move out of x because it is borrowed |
finish with the borrow first |
| E0506 | cannot assign to x because it is borrowed |
finish with the borrow first, or mem::replace |
| E0507 | cannot move out of x which is behind a mutable reference |
take, as_ref |
| E0596 | cannot borrow *x as mutable, as it is behind a & reference |
take &mut, or add mut |
| E0106 | missing lifetime specifier | an id instead of a reference |
| E0597 | x does not live long enough |
keep the owner longer, or own the data |
| E0515 | cannot return reference to local variable x |
return an owned value |
| E0716 | temporary value dropped while borrowed | bind the temporary with let |
The habit you bring, the error you meet
Use after move (E0382)
fn total(scores: Vec<u32>) -> u32 { scores.iter().sum()}
fn main() { let scores = vec![90, 72, 85]; let sum = total(scores); println!("sum: {sum}"); println!("count: {}", scores.len());}error[E0382]: borrow of moved value: `scores` --> src/main.rs:9:27 | 6 | let scores = vec![90, 72, 85]; | ------ move occurs because `scores` has type `Vec<u32>`, which does not implement the `Copy` trait 7 | let sum = total(scores); | ------ value moved here 8 | println!("sum: {sum}"); 9 | println!("count: {}", scores.len()); | ^^^^^^ value borrowed here after move | note: consider changing this parameter type in function `total` to borrow instead if owning the value isn't necessary --> src/main.rs:1:18 | 1 | fn total(scores: Vec<u32>) -> u32 { | ----- ^^^^^^^^ this parameter takes ownership of the value | | | in this function help: consider cloning the value if the performance cost is acceptable | 7 | let sum = total(scores.clone()); | ++++++++
total takes the Vec by value, so scores belongs to it after the call. The fix depends on what the function does with it:
// Fix 1: the function only reads, so its signature should borrow.fn total(scores: &[u32]) -> u32 { scores.iter().sum()}
// This one keeps what it is given, so it must own it.struct Report { scores: Vec<u32>,}
impl Report { fn new(scores: Vec<u32>) -> Report { Report { scores } }}
fn main() { let scores = vec![90, 72, 85]; println!("total: {}", total(&scores));
// Fix 2: clone, when the caller needs its own copy too. let report = Report::new(scores.clone()); println!( "clone, same buffer: {}", report.scores.as_ptr() == scores.as_ptr() );
// Fix 3: give it away, and stop using the original. let before = scores.as_ptr(); let report = Report::new(scores); println!("move, same buffer: {}", report.scores.as_ptr() == before);}total: 247 clone, same buffer: false move, same buffer: true
- It only reads: change the signature to borrow (
&[u32]). Nothing is copied. - It keeps the value, and you need yours too: clone. The output shows a different buffer: an allocation and a copy of every element. Fine for a small value built once; a smell in a loop, when the function only reads, or when the copies must stay in sync (clone or share).
- It keeps the value, and you are done with it: move it. Same buffer.
Two borrows at once (E0499, E0502)
A reference still used after a push is E0502. A borrow ends at its last use (Mutable borrows), so copy the small value out first (let n = v.len();) or move the use above the push. A Drop impl counts as a use at the closing } (E0499 here):
struct Guard<'a>(&'a mut Vec<i32>);
impl Drop for Guard<'_> { fn drop(&mut self) { println!("guard dropped"); }}
fn main() { let mut list = vec![1]; let guard = Guard(&mut list); guard.0.push(5); list.push(2);}error[E0499]: cannot borrow `list` as mutable more than once at a time --> src/main.rs:13:5 | 11 | let guard = Guard(&mut list); | --------- first mutable borrow occurs here 12 | guard.0.push(5); 13 | list.push(2); | ^^^^ second mutable borrow occurs here 14 | } | - first borrow might be used here, when `guard` is dropped and runs the `Drop` code for type `Guard`
The note says so: “first borrow might be used here, when guard is dropped”. drop(guard) after its last use ends the borrow there.
Two elements. The compiler does not look at index values, so &mut v[0] and &mut v[3] both borrow the whole Vec (Mutable borrows). swap swaps, split_at_mut gives two halves that cannot overlap, and get_disjoint_mut (Rust 1.86) checks the indices when it runs:
use std::mem;
fn main() { // Swapping: there is a method. let mut v = vec![1, 2, 3, 4]; v.swap(0, 3); println!("swap: {v:?}");
// Two &mut that cannot overlap: split the list in two. let mut v = vec![1, 2, 3, 4]; let (left, right) = v.split_at_mut(2); mem::swap(&mut left[0], &mut right[1]); println!("split_at_mut: {v:?}");
// Two &mut chosen by index: the check happens at run time. let mut v = vec![1, 2, 3, 4]; if let Ok([a, b]) = v.get_disjoint_mut([0, 3]) { mem::swap(a, b); } println!("get_disjoint_mut: {v:?}"); println!("same index: {}", v.get_disjoint_mut([1, 1]).is_err());}swap: [4, 2, 3, 1] split_at_mut: [4, 2, 3, 1] get_disjoint_mut: [4, 2, 3, 1] same index: true
Two fields and a method. The gotcha failed because self.note(..) takes &mut self, which borrows all of self, including the items being read. A field access names one field, so items and log are different places:
use std::mem;
struct Item { name: String, count: u32,}
// Fix 2 needs a function that takes only the field it changes.fn note(log: &mut Vec<String>, text: &str) { log.push(text.to_string());}
struct Inventory { items: Vec<Item>, log: Vec<String>,}
impl Inventory { fn note(&mut self, text: &str) { self.log.push(text.to_string()); }
// Fix 1: name the field. `items` and `log` are separate places. fn check_direct(&mut self) { for item in &self.items { if item.count == 0 { self.log.push(format!("direct: {}", item.name)); } } }
// Fix 2: a function that takes the one field it needs. fn check_field(&mut self) { for item in &self.items { if item.count == 0 { note(&mut self.log, &format!("field: {}", item.name)); } } }
// Fix 3: take the field out, call the method, put the field back. // While `items` is out, `self.items` is an empty Vec. fn check_take(&mut self) { let items = mem::take(&mut self.items); for item in &items { if item.count == 0 { self.note(&format!("take: {}", item.name)); } } self.items = items; }}
// Fix 4: make the log its own type, so a method on it borrows only the log.struct Log { lines: Vec<String>,}
impl Log { fn note(&mut self, text: &str) { self.lines.push(text.to_string()); }}
struct Shop { items: Vec<Item>, log: Log,}
impl Shop { fn check(&mut self) { for item in &self.items { if item.count == 0 { self.log.note(&format!("parts: {}", item.name)); } } }}
fn items() -> Vec<Item> { vec![ Item { name: "bolt".to_string(), count: 0, }, Item { name: "nut".to_string(), count: 12, }, ]}
fn main() { let mut inv = Inventory { items: items(), log: Vec::new(), }; inv.check_direct(); inv.check_field(); let buffer = inv.items.as_ptr(); inv.check_take(); println!("log: {:?}", inv.log); println!( "items put back: {}, same buffer: {}", inv.items.len(), inv.items.as_ptr() == buffer );
let mut shop = Shop { items: items(), log: Log { lines: Vec::new() }, }; shop.check(); println!("shop log: {:?}", shop.log.lines);}log: ["direct: bolt", "field: bolt", "take: bolt"] items put back: 2, same buffer: true shop log: ["parts: bolt"]
| Fix | What it costs |
|---|---|
Name the field: self.log.push(..) |
you give up the helper; its logic is repeated |
| A function that takes only the field it needs | callers pass the field |
| Work out what to do first, then call the method (the gotcha’s fix) | an extra Vec and a second pass |
Take the field out with mem::take, call the method, put it back |
items is empty in between, and an early return or ? forgets to put it back |
Make the field its own type (Log) |
one more type, which often matches the data anyway |
A closure that calls the method captures all of self the same way:
struct Item { name: String, count: u32,}
struct Inventory { items: Vec<Item>, log: Vec<String>,}
impl Inventory { fn note(&mut self, text: &str) { self.log.push(text.to_string()); }
fn check(&mut self) { self.items .iter() .filter(|item| item.count == 0) .for_each(|item| self.note(&item.name)); }}
fn main() { let mut inv = Inventory { items: vec![Item { name: "bolt".to_string(), count: 0, }], log: Vec::new(), }; inv.check(); println!("log: {:?}", inv.log);}error[E0500]: closure requires unique access to `*self` but it is already borrowed --> src/main.rs:20:23 | 17 | self.items | ---------- borrow occurs here ... 20 | .for_each(|item| self.note(&item.name)); | -------- ^^^^^^ ---- second borrow occurs due to use of `*self` in closure | | | | | closure construction occurs here | first borrow later used by call
Naming the field in the closure (self.log.push(..)) captures only log. Closures explains capture.
Handing out a &mut
Ask for a &mut to just the part you change: opt.as_mut() or as_deref_mut() on an Option, iter_mut(), last_mut() or iter_mut().find(..) on a Vec, get_mut(key) on a map. Use it inside if let Some(x) = .., and do other work after it, where the &mut is over. One shape still fails: returning the &mut when the key is found and inserting otherwise.
use std::collections::HashMap;
fn counter<'a>(map: &'a mut HashMap<String, u32>, key: &str) -> &'a mut u32 { if let Some(n) = map.get_mut(key) { return n; } map.insert(key.to_string(), 0); map.get_mut(key).unwrap()}
fn main() { let mut map = HashMap::new(); *counter(&mut map, "pear") += 1; *counter(&mut map, "pear") += 1; println!("{map:?}");}error[E0499]: cannot borrow `*map` as mutable more than once at a time --> src/main.rs:7:5 | 3 | fn counter<'a>(map: &'a mut HashMap<String, u32>, key: &str) -> &'a mut u32 { | -- lifetime `'a` defined here 4 | if let Some(n) = map.get_mut(key) { | --- first mutable borrow occurs here 5 | return n; | - returning this value requires that `*map` is borrowed for `'a` 6 | } 7 | map.insert(key.to_string(), 0); | ^^^ second mutable borrow occurs here error[E0499]: cannot borrow `*map` as mutable more than once at a time --> src/main.rs:8:5 | 3 | fn counter<'a>(map: &'a mut HashMap<String, u32>, key: &str) -> &'a mut u32 { | -- lifetime `'a` defined here 4 | if let Some(n) = map.get_mut(key) { | --- first mutable borrow occurs here 5 | return n; | - returning this value requires that `*map` is borrowed for `'a` ... 8 | map.get_mut(key).unwrap() | ^^^ second mutable borrow occurs here
Both errors are E0499 on 1.99.1. entry looks up once and gives back the slot:
use std::collections::HashMap;
fn counter<'a>(map: &'a mut HashMap<String, u32>, key: &str) -> &'a mut u32 { map.entry(key.to_string()).or_default()}
fn main() { let mut map = HashMap::new(); *counter(&mut map, "pear") += 1; *counter(&mut map, "pear") += 1; println!("{map:?}");}{"pear": 2}The cost: entry takes the key by value, so the String is built even when the key is there. Maps has the simpler get-then-insert shape.
Moving out of a borrow (E0507)
Behind a &self or &mut self you cannot take a value out of a field and leave a hole (Ownership and Mutable borrows show the error). Its help: suggests clone, which compiles and is wrong here:
struct Session { token: Option<String>,}
impl Session { // The compiler's `help:` suggestion for E0507: it compiles, and it is not a logout. fn logout(&mut self) -> Option<String> { self.token.clone() }}
fn main() { let mut session = Session { token: Some(String::from("abc123")), }; println!("logout: {:?}", session.logout()); println!("token afterwards: {:?}", session.token);}logout: Some("abc123")
token afterwards: Some("abc123")logout must leave the session without a token, and a clone leaves the old one in place. Choose between looking, taking and copying:
| You want | Use | What is left behind |
|---|---|---|
to look at the text in an Option<String> |
opt.as_deref(), or opt.as_ref() for a &String |
opt, unchanged |
the value, and None in its place |
opt.take() |
None |
a whole String or Vec, and an empty one in its place |
mem::take(&mut x) |
the empty value (the type needs a default) |
| the old value, and one you choose in its place | mem::replace(&mut x, new) |
new |
| a second, independent value | x.clone() |
x, unchanged; you paid for a copy |
Moved or assigned while borrowed (E0505, E0506)
fn send(msg: String) -> usize { msg.len()}
fn main() { let msg = String::from("hello world"); let first = msg.split(' ').next().unwrap(); let sent = send(msg); println!("sent {sent} bytes, first word {first}");}error[E0505]: cannot move out of `msg` because it is borrowed --> src/main.rs:8:21 | 6 | let msg = String::from("hello world"); | --- binding `msg` declared here 7 | let first = msg.split(' ').next().unwrap(); | --- borrow of `msg` occurs here 8 | let sent = send(msg); | ^^^ move out of `msg` occurs here 9 | println!("sent {sent} bytes, first word {first}"); | ----- borrow later used here
fn main() { let mut position = String::from("start"); let before = &position; position = String::from("middle"); println!("moved from {before} to {position}");}error[E0506]: cannot assign to `position` because it is borrowed --> src/main.rs:4:5 | 3 | let before = &position; | --------- `position` is borrowed here 4 | position = String::from("middle"); | ^^^^^^^^ `position` is assigned to here but it was already borrowed 5 | println!("moved from {before} to {position}"); | ------ borrow later used here
first and before are still used after the move and the overwrite. Finish with the borrow first, or own a copy of the piece you need. For the assignment, mem::replace takes &mut position for the call only, so no borrow is kept:
use std::mem;
fn send(msg: String) -> usize { msg.len()}
fn main() { // E0505: use the borrowed word first, or copy it out of the message. let msg = String::from("hello world"); let first = msg.split(' ').next().unwrap().to_string(); let sent = send(msg); println!("sent {sent} bytes, first word {first}");
// E0506: take the old value out as you put the new one in. let mut position = String::from("start"); let before = mem::replace(&mut position, String::from("middle")); println!("moved from {before} to {position}");}sent 11 bytes, first word hello moved from start to middle
Changing a collection while looping over it
For loops over collections did this with a Vec. A map is the same clash:
use std::collections::HashMap;
fn main() { let mut stock: HashMap<&str, u32> = HashMap::from([("bolt", 0), ("nut", 12), ("washer", 0)]); for (name, count) in &stock { if *count == 0 { stock.remove(name); } } println!("{} items left", stock.len());}error[E0502]: cannot borrow `stock` as mutable because it is also borrowed as immutable --> src/main.rs:7:13 | 5 | for (name, count) in &stock { | - ------ | | | | | immutable borrow occurs here | _____| immutable borrow later used here | | 6 | | if *count == 0 { 7 | | stock.remove(name); | | ^^^^^^^^^^^^^^^^^^ mutable borrow occurs here 8 | | } 9 | | } | |_____- this for loop borrows `stock` immutably, preventing mutation within its body | = help: consider using an index-based loop instead, or collecting modifications into a separate collection
use std::collections::HashMap;
fn stock() -> HashMap<&'static str, u32> { HashMap::from([("bolt", 0), ("nut", 12), ("washer", 0)])}
fn sorted(mut names: Vec<&str>) -> Vec<&str> { names.sort(); names}
fn main() { // Just drop them: retain keeps what the closure accepts. let mut a = stock(); a.retain(|_, count| *count > 0); println!("retain: {:?}", sorted(a.keys().copied().collect()));
// Need the removed ones too: extract_if hands them to you. let mut b = stock(); let gone: Vec<&str> = b .extract_if(|_, count| *count == 0) .map(|(name, _)| name) .collect(); println!( "extract_if removed {:?}, kept {:?}", sorted(gone), sorted(b.keys().copied().collect()) );
// extract_if is lazy: an iterator that is never consumed removes nothing. let mut d = stock(); let _ = d.extract_if(|_, count| *count == 0); println!("unconsumed extract_if: {} entries left", d.len());
// Decide in one pass, change in the next. The keys are copied (they are &str). let mut c = stock(); let empty: Vec<&str> = c .iter() .filter(|(_, count)| **count == 0) .map(|(name, _)| *name) .collect(); for name in &empty { c.remove(name); } println!("two passes: {:?}", sorted(c.keys().copied().collect()));}retain: ["nut"] extract_if removed ["bolt", "washer"], kept ["nut"] unconsumed extract_if: 3 entries left two passes: ["nut"]
| You want | Use | Cost |
|---|---|---|
| to drop entries | retain |
a closure that says which to keep |
| to drop entries and keep them | extract_if (Rust 1.88) |
an iterator you never consume removes nothing |
| to decide now and change later | collect the keys first | an extra Vec, and a second lookup per key |
A reference that outlives its owner (E0515, E0597, E0716)
Returning references covers E0515, and Lifetimes covers E0597 and E0716 (a temporary dropped at the end of its statement; bind it with let). One shape shows up in loops: a value built inside the body, with a reference stored outside.
fn main() { let text = " tea \n coffee \n milk"; let mut longest = ""; for line in text.lines() { let trimmed = line.trim().to_string(); if trimmed.len() > longest.len() { longest = &trimmed; } } println!("longest: {longest}");}error[E0597]: `trimmed` does not live long enough --> src/main.rs:7:23 | 5 | let trimmed = line.trim().to_string(); | ------- binding `trimmed` declared here 6 | if trimmed.len() > longest.len() { 7 | longest = &trimmed; | ^^^^^^^^ borrowed value does not live long enough 8 | } 9 | } | - `trimmed` dropped here while still borrowed 10 | println!("longest: {longest}"); | ------- borrow later used here
trimmed is dropped at each iteration’s end, and longest outlives it. Borrow from something that lives long enough, or move the owned value out:
fn main() { let text = " tea \n coffee \n milk";
// Borrow from `text`, which lives for the whole function: no String needed. let mut longest = ""; for line in text.lines() { let trimmed = line.trim(); if trimmed.len() > longest.len() { longest = trimmed; } } println!("borrowed: {longest}");
// If each line really is built in the loop, move the String out instead. let mut longest = String::new(); for line in text.lines() { let upper = line.trim().to_uppercase(); if upper.len() > longest.len() { longest = upper; } } println!("owned: {longest}");}borrowed: coffee owned: COFFEE
The second version moves each new String into longest, which drops the previous one: nothing is borrowed.
Parent pointers and graphs: indices, not references
A child holding a bare &Dept of its parent is E0106. Once the lifetime is named, the child freezes the parent while it is still in use:
struct Dept { name: String, parent: Option<&Dept>,}
fn main() { let hq = Dept { name: String::from("HQ"), parent: None, }; println!("{}", hq.name);}error[E0106]: missing lifetime specifier --> src/main.rs:3:20 | 3 | parent: Option<&Dept>, | ^ expected named lifetime parameter | help: consider introducing a named lifetime parameter | 1 ~ struct Dept<'a> { 2 | name: String, 3 ~ parent: Option<&'a Dept>,
struct Dept<'a> { name: String, parent: Option<&'a Dept<'a>>,}
fn main() { let mut hq = Dept { name: String::from("HQ"), parent: None, }; let eng = Dept { name: String::from("Eng"), parent: Some(&hq), }; hq.name = String::from("Acme"); println!("{} reports to {}", eng.name, eng.parent.unwrap().name);}error[E0506]: cannot assign to `hq.name` because it is borrowed --> src/main.rs:15:5 | 13 | parent: Some(&hq), | --- `hq.name` is borrowed here 14 | }; 15 | hq.name = String::from("Acme"); | ^^^^^^^ `hq.name` is assigned to here but it was already borrowed 16 | println!("{} reports to {}", eng.name, eng.parent.unwrap().name); | -------- borrow later used here
A struct holding a reference into its own field is the same trap: moving text in while first borrows it is E0505:
struct Doc<'a> { text: String, first: &'a str,}
fn main() { let text = String::from("hello world"); let first = text.split(' ').next().unwrap(); let doc = Doc { text, first }; println!("{} / {}", doc.text, doc.first);}error[E0505]: cannot move out of `text` because it is borrowed --> src/main.rs:9:21 | 7 | let text = String::from("hello world"); | ---- binding `text` declared here 8 | let first = text.split(' ').next().unwrap(); | ---- borrow of `text` occurs here 9 | let doc = Doc { text, first }; | ^^^^ ----- borrow later used here | | | move out of `text` occurs here
Store offsets and slice on demand, or keep owner and borrower as separate values. For a tree or graph, keep every node in one Vec and refer to nodes by position. The Vec is the owner (an arena), and an Id is a number, which borrows nothing:
use std::mem::size_of;
#[derive(Clone, Copy, Debug)]struct Id(usize);
struct Dept { name: String, parent: Option<Id>,}
struct Org { depts: Vec<Dept>,}
impl Org { fn add(&mut self, name: &str, parent: Option<Id>) -> Id { self.depts.push(Dept { name: name.to_string(), parent, }); Id(self.depts.len() - 1) }
fn rename(&mut self, id: Id, name: &str) { self.depts[id.0].name = name.to_string(); }
// Walk up the parents and join the names, root first. fn path(&self, id: Id) -> String { let mut names = vec![self.depts[id.0].name.as_str()]; let mut at = self.depts[id.0].parent; while let Some(p) = at { names.push(&self.depts[p.0].name); at = self.depts[p.0].parent; } names.reverse(); names.join(" / ") }}
fn main() { let mut org = Org { depts: Vec::new() }; let hq = org.add("HQ", None); let eng = org.add("Eng", Some(hq)); let web = org.add("Web", Some(eng)); for (i, d) in org.depts.iter().enumerate() { println!("depts[{i}] = {}, parent {:?}", d.name, d.parent); } println!("path: {}", org.path(web));
// The parent can change while the children still point at it. org.rename(hq, "Acme"); println!("after rename: {}", org.path(web)); println!("size of Id: {} bytes", size_of::<Id>());
// The price: an Id is only a number. Nothing says which Org it came from. let mut other = Org { depts: Vec::new() }; other.add("Sales", None); other.add("Field", None); println!("eng's id in another org: {}", other.path(eng));}depts[0] = HQ, parent None depts[1] = Eng, parent Some(Id(0)) depts[2] = Web, parent Some(Id(1)) path: HQ / Eng / Web after rename: Acme / Eng / Web size of Id: 8 bytes eng's id in another org: Field
Costs: an Id is only a number. Nothing says which Org it came from: the last line gets a valid but wrong department from another one. Removing a node leaves later ids pointing at the wrong entries, unless you leave a hole (Option<Dept>) or version the ids with a counter in each slot and each id, as the slotmap crate does.
Clone or share: when Rc<RefCell<T>> is the honest answer
Cloning to get past an error gives you a second value. If two parts of the program must see each other’s changes, that is a bug:
use std::cell::RefCell;use std::rc::Rc;
#[derive(Clone, Debug)]struct Settings { volume: u8,}
fn main() { // clone: two independent copies. Changing one leaves the other alone. let original = Settings { volume: 3 }; let mut copy = original.clone(); copy.volume = 9; println!("clone: original {}, copy {}", original.volume, copy.volume);
// Rc<RefCell<_>>: one value, two handles. A change through either is seen by both. let shared = Rc::new(RefCell::new(Settings { volume: 3 })); let other = Rc::clone(&shared); other.borrow_mut().volume = 9; println!( "shared: {} and {}", shared.borrow().volume, other.borrow().volume ); println!("owners: {}", Rc::strong_count(&shared));}clone: original 3, copy 9 shared: 9 and 9 owners: 2
When the design really is “many owners of one changing value”, Rc<RefCell<T>> says so (Box, Rc and RefCell). The price: the borrow rules are checked while the program runs, so a mistake is a panic, and Rc cannot cross threads.
| The situation | Usual answer |
|---|---|
| a small value, copied once at a boundary | clone |
a counter or cache that a &self method updates |
Cell<T> for a Copy value |
| two parts must see the same changes | Rc<RefCell<T>> (Arc and a lock across threads: Shared state) |
it keeps coming back as one big &mut self |
restructure (below) |
Going deeper
When the error does not go away: redesign
The signs are at the top. Ownership lists the broad changes (one owner per piece of state, ids for references), Returning references returns results up, and Lifetimes weighs owning against borrowing. Two belong here:
- Split a struct when its fields change at different times. A method that wants
itemsread-only andlogwritable is describing two types (theLogfix above). - Decide with
&self, then apply with&mut self. Read everything, return a plan of positions, then change. The cost is the plan, and the list must not change in between.
struct Pool { jobs: Vec<u32>, done: Vec<u32>,}
impl Pool { // Decide with `&self`: nothing changes, so any number of borrows is fine. fn finished(&self) -> Vec<usize> { let mut plan = Vec::new(); for (i, job) in self.jobs.iter().enumerate() { if job % 2 == 0 { plan.push(i); } } plan }
// Apply with `&mut self`: the plan holds positions, not borrows. fn apply(&mut self, plan: Vec<usize>) { for i in plan.into_iter().rev() { let job = self.jobs.remove(i); self.done.push(job); } }
fn tick(&mut self) { let plan = self.finished(); self.apply(plan); }}
fn main() { let mut pool = Pool { jobs: vec![1, 2, 3, 4, 5, 6], done: Vec::new(), }; pool.tick(); println!("jobs: {:?}, done: {:?}", pool.jobs, pool.done);}jobs: [1, 3, 5], done: [6, 4, 2]
apply removes from the back so earlier positions stay valid.
unsafe does not switch the checker off: it lets you do what the compiler cannot check, and a mistake there is yours. The checker also rejects some programs that would run fine, such as the conditional return in Lifetimes; the fix is a shape it accepts, not a trick.
Practice
Try it
Each starter compiles and stops at todo!. The first draft you would write anywhere fails with the error named below: write it, read the error, then fix it. The checks count clones, compare buffer addresses and forbid unsafe, so copying the data does not pass.
Flag low stock
Write flag_low: for each item with a count below 5, in order, call self.note with low: NAME (COUNT). Your first draft fails with E0502. Call note itself (it numbers each line), and do not copy the items.
#![forbid(unsafe_code)]
use std::sync::atomic::{AtomicUsize, Ordering};
static CLONES: AtomicUsize = AtomicUsize::new(0);
struct Item { name: String, count: u32,}
// Counts every clone, so a "fix" that copies the items shows up in the checks.impl Clone for Item { fn clone(&self) -> Item { CLONES.fetch_add(1, Ordering::SeqCst); Item { name: self.name.clone(), count: self.count, } }}
struct Shop { items: Vec<Item>, log: Vec<String>, notes_made: usize,}
impl Shop { fn new(items: &[(&str, u32)]) -> Shop { let items = items .iter() .map(|(name, count)| Item { name: name.to_string(), count: *count, }) .collect(); Shop { items, log: Vec::new(), notes_made: 0, } }
// Every log line goes through here, and `note` numbers it. fn note(&mut self, text: &str) { self.notes_made += 1; self.log.push(format!("#{} {}", self.notes_made, text)); }
// For each item with a count below 5, in order, call `self.note` with // "low: NAME (COUNT)". Call `note` itself, and do not copy the items. fn flag_low(&mut self) { todo!("write the loop you would write anywhere else, read the error, then change it") }}
fn main() { let mut shop = Shop::new(&[("bolt", 0), ("nut", 12), ("washer", 4), ("screw", 5)]); shop.flag_low(); assert_eq!( shop.log, ["#1 low: bolt (0)", "#2 low: washer (4)"], "every line must come from `note`, in order" ); assert_eq!(shop.notes_made, 2); assert_eq!(shop.items.len(), 4); assert_eq!(CLONES.load(Ordering::SeqCst), 0, "do not clone the items");
let mut empty = Shop::new(&[]); empty.flag_low(); assert!(empty.log.is_empty());
shop.flag_low(); assert_eq!(shop.log.len(), 4, "calling it again adds to the log"); assert_eq!(shop.log[3], "#4 low: washer (4)"); println!("all checks passed");}Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.
Show a solution
#![forbid(unsafe_code)]
use std::sync::atomic::{AtomicUsize, Ordering};
static CLONES: AtomicUsize = AtomicUsize::new(0);
struct Item { name: String, count: u32,}
// Counts every clone, so a "fix" that copies the items shows up in the checks.impl Clone for Item { fn clone(&self) -> Item { CLONES.fetch_add(1, Ordering::SeqCst); Item { name: self.name.clone(), count: self.count, } }}
struct Shop { items: Vec<Item>, log: Vec<String>, notes_made: usize,}
impl Shop { fn new(items: &[(&str, u32)]) -> Shop { let items = items .iter() .map(|(name, count)| Item { name: name.to_string(), count: *count, }) .collect(); Shop { items, log: Vec::new(), notes_made: 0, } }
// Every log line goes through here, and `note` numbers it. fn note(&mut self, text: &str) { self.notes_made += 1; self.log.push(format!("#{} {}", self.notes_made, text)); }
// For each item with a count below 5, in order, call `self.note` with // "low: NAME (COUNT)". Call `note` itself, and do not copy the items. fn flag_low(&mut self) { let mut low = Vec::new(); for item in &self.items { if item.count < 5 { low.push(format!("low: {} ({})", item.name, item.count)); } } for line in low { self.note(&line); } }}
fn main() { let mut shop = Shop::new(&[("bolt", 0), ("nut", 12), ("washer", 4), ("screw", 5)]); shop.flag_low(); assert_eq!( shop.log, ["#1 low: bolt (0)", "#2 low: washer (4)"], "every line must come from `note`, in order" ); assert_eq!(shop.notes_made, 2); assert_eq!(shop.items.len(), 4); assert_eq!(CLONES.load(Ordering::SeqCst), 0, "do not clone the items");
let mut empty = Shop::new(&[]); empty.flag_low(); assert!(empty.log.is_empty());
shop.flag_low(); assert_eq!(shop.log.len(), 4, "calling it again adds to the log"); assert_eq!(shop.log[3], "#4 low: washer (4)"); println!("all checks passed");}all checks passed
Settle a ledger
Write settle: apply each entry in order with self.apply (it needs &mut self), and return the first Err. Your first draft fails with E0502. Every entry must stay in the ledger, in the same buffer, even when apply fails.
#![forbid(unsafe_code)]
use std::sync::atomic::{AtomicUsize, Ordering};
static CLONES: AtomicUsize = AtomicUsize::new(0);
#[derive(Debug, PartialEq)]struct Entry { memo: String, amount: i64,}
// Counts every clone, so a "fix" that copies an entry shows up in the checks.impl Clone for Entry { fn clone(&self) -> Entry { CLONES.fetch_add(1, Ordering::SeqCst); Entry { memo: self.memo.clone(), amount: self.amount, } }}
struct Ledger { entries: Vec<Entry>, balance: i64, limit: i64, journal: Vec<String>,}
impl Ledger { fn new(limit: i64, entries: &[(&str, i64)]) -> Ledger { Ledger { entries: entries .iter() .map(|(memo, amount)| Entry { memo: memo.to_string(), amount: *amount, }) .collect(), balance: 0, limit, journal: Vec::new(), } }
// Applies one entry, unless the balance would pass `limit`. Leave this as it is. fn apply(&mut self, entry: &Entry) -> Result<(), String> { if self.balance + entry.amount > self.limit { return Err(format!("{} would pass the limit", entry.memo)); } self.balance += entry.amount; self.journal.push(format!("applied {}", entry.memo)); Ok(()) }
// Apply every entry in `entries`, in order, and stop at the first `Err`, which you // return. The entries stay in `entries` either way, in the same buffer. fn settle(&mut self) -> Result<(), String> { todo!("write the loop you would write anywhere else, read the error, then change it") }}
fn main() { let mut ledger = Ledger::new(100, &[("rent", 60), ("food", 30), ("fun", 20), ("tea", 5)]); let buffer = ledger.entries.as_ptr();
// The third entry would pass the limit: stop there, keep every entry. assert_eq!(ledger.settle(), Err("fun would pass the limit".to_string())); assert_eq!(ledger.balance, 90); assert_eq!(ledger.journal, ["applied rent", "applied food"]); assert_eq!( ledger.entries.len(), 4, "an early return must not lose the entries" ); assert_eq!( ledger.entries.as_ptr(), buffer, "the entries were copied, not put back" ); assert_eq!(ledger.entries[3].memo, "tea");
// With room, every entry is applied, in order. let mut roomy = Ledger::new(1000, &[("a", 1), ("b", 2), ("c", 3)]); assert_eq!(roomy.settle(), Ok(())); assert_eq!(roomy.balance, 6); assert_eq!(roomy.journal, ["applied a", "applied b", "applied c"]); assert_eq!(roomy.entries.len(), 3);
let mut empty = Ledger::new(10, &[]); assert_eq!(empty.settle(), Ok(())); assert!(empty.journal.is_empty());
assert_eq!(CLONES.load(Ordering::SeqCst), 0, "do not clone the entries"); println!("all checks passed");}Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.
Show a solution
#![forbid(unsafe_code)]
use std::sync::atomic::{AtomicUsize, Ordering};
static CLONES: AtomicUsize = AtomicUsize::new(0);
#[derive(Debug, PartialEq)]struct Entry { memo: String, amount: i64,}
// Counts every clone, so a "fix" that copies an entry shows up in the checks.impl Clone for Entry { fn clone(&self) -> Entry { CLONES.fetch_add(1, Ordering::SeqCst); Entry { memo: self.memo.clone(), amount: self.amount, } }}
struct Ledger { entries: Vec<Entry>, balance: i64, limit: i64, journal: Vec<String>,}
impl Ledger { fn new(limit: i64, entries: &[(&str, i64)]) -> Ledger { Ledger { entries: entries .iter() .map(|(memo, amount)| Entry { memo: memo.to_string(), amount: *amount, }) .collect(), balance: 0, limit, journal: Vec::new(), } }
// Applies one entry, unless the balance would pass `limit`. Leave this as it is. fn apply(&mut self, entry: &Entry) -> Result<(), String> { if self.balance + entry.amount > self.limit { return Err(format!("{} would pass the limit", entry.memo)); } self.balance += entry.amount; self.journal.push(format!("applied {}", entry.memo)); Ok(()) }
// Apply every entry in `entries`, in order, and stop at the first `Err`, which you // return. The entries stay in `entries` either way, in the same buffer. fn settle(&mut self) -> Result<(), String> { let entries = std::mem::take(&mut self.entries); let mut result = Ok(()); for entry in &entries { if let Err(message) = self.apply(entry) { result = Err(message); break; } } self.entries = entries; result }}
fn main() { let mut ledger = Ledger::new(100, &[("rent", 60), ("food", 30), ("fun", 20), ("tea", 5)]); let buffer = ledger.entries.as_ptr();
// The third entry would pass the limit: stop there, keep every entry. assert_eq!(ledger.settle(), Err("fun would pass the limit".to_string())); assert_eq!(ledger.balance, 90); assert_eq!(ledger.journal, ["applied rent", "applied food"]); assert_eq!( ledger.entries.len(), 4, "an early return must not lose the entries" ); assert_eq!( ledger.entries.as_ptr(), buffer, "the entries were copied, not put back" ); assert_eq!(ledger.entries[3].memo, "tea");
// With room, every entry is applied, in order. let mut roomy = Ledger::new(1000, &[("a", 1), ("b", 2), ("c", 3)]); assert_eq!(roomy.settle(), Ok(())); assert_eq!(roomy.balance, 6); assert_eq!(roomy.journal, ["applied a", "applied b", "applied c"]); assert_eq!(roomy.entries.len(), 3);
let mut empty = Ledger::new(10, &[]); assert_eq!(empty.settle(), Ok(())); assert!(empty.journal.is_empty());
assert_eq!(CLONES.load(Ordering::SeqCst), 0, "do not clone the entries"); println!("all checks passed");}all checks passed
Teams by id
Write relabel: set a team’s path to its parent’s path, a slash and its own name, then do the same below it. Your first draft fails with E0502: it holds &mut self.teams[i] while reading the parent, or loops over the teams while calling self.relabel. The checks count clones of a Team.
#![forbid(unsafe_code)]
use std::sync::atomic::{AtomicUsize, Ordering};
static CLONES: AtomicUsize = AtomicUsize::new(0);
#[derive(Clone, Copy, Debug, PartialEq)]struct Id(usize);
struct Team { name: String, size: u32, parent: Option<Id>, path: String,}
// Counts every clone of a team, so a "fix" that copies the teams shows up in the checks.impl Clone for Team { fn clone(&self) -> Team { CLONES.fetch_add(1, Ordering::SeqCst); Team { name: self.name.clone(), size: self.size, parent: self.parent, path: self.path.clone(), } }}
// All the teams live in one Vec; a team names its parent by Id, not by reference.#[derive(Clone)]struct Org { teams: Vec<Team>,}
impl Org { fn add(&mut self, name: &str, size: u32, parent: Option<Id>) -> Id { let path = match parent { Some(p) => format!("{}/{}", self.teams[p.0].path, name), None => name.to_string(), }; self.teams.push(Team { name: name.to_string(), size, parent, path, }); Id(self.teams.len() - 1) }
// The team's size plus the size of every team below it, at any depth. fn total(&self, id: Id) -> u32 { let mut sum = self.teams[id.0].size; for (i, team) in self.teams.iter().enumerate() { if team.parent == Some(id) { sum += self.total(Id(i)); } } sum }
// Set `path` to the parent's path, a slash and the team's name (a root's path is // its name), then do the same for every team below it. fn relabel(&mut self, id: Id) { todo!("read the parent, write the child; then the teams below") }
// Move `id` (and everything below it) under `new_parent`, and relabel. // Err if that would make a team its own ancestor: `new_parent` is `id` or lies below it. fn reparent(&mut self, id: Id, new_parent: Id) -> Result<(), String> { let mut at = Some(new_parent); while let Some(p) = at { if p == id { return Err(format!("{} cannot go under itself", self.teams[id.0].name)); } at = self.teams[p.0].parent; } self.teams[id.0].parent = Some(new_parent); self.relabel(id); Ok(()) }}
fn main() { let mut org = Org { teams: Vec::new() }; let all = org.add("all", 1, None); let eng = org.add("eng", 10, Some(all)); let web = org.add("web", 4, Some(eng)); let ops = org.add("ops", 3, Some(all)); assert_eq!(org.total(web), 4); assert_eq!(org.total(eng), 14); assert_eq!(org.total(all), 18); assert_eq!(org.teams[web.0].path, "all/eng/web");
// Growing the Vec does not invalidate any Id. for i in 0..100 { org.add("temp", 0, Some(ops)); assert_eq!(org.total(ops), 3, "temp {i} has size 0"); } assert_eq!(org.teams[web.0].name, "web");
// Moving a team moves everything below it, and their paths follow. let leaf = org.add("leaf", 2, Some(web)); assert_eq!(org.reparent(web, ops), Ok(())); assert_eq!(org.total(eng), 10); assert_eq!(org.total(ops), 9); assert_eq!(org.total(all), 20); assert_eq!(org.teams[web.0].path, "all/ops/web"); assert_eq!(org.teams[leaf.0].path, "all/ops/web/leaf");
// `web` now has a smaller index than its parent `ops`: do not rely on the order. assert_eq!(org.reparent(ops, eng), Ok(())); assert_eq!(org.teams[ops.0].path, "all/eng/ops"); assert_eq!(org.teams[web.0].path, "all/eng/ops/web"); assert_eq!(org.teams[leaf.0].path, "all/eng/ops/web/leaf");
// A team cannot go under itself or under one of its own descendants. assert!(org.reparent(all, web).is_err()); assert!(org.reparent(eng, eng).is_err()); assert!(org.reparent(eng, leaf).is_err()); assert_eq!(org.total(all), 20, "a refused move changes nothing"); assert_eq!(org.teams[eng.0].path, "all/eng");
// The teams are moved, not copied. assert_eq!(CLONES.load(Ordering::SeqCst), 0, "do not clone the teams");
// A copy of the whole organisation is independent of the original. let mut copy = org.clone(); copy.reparent(leaf, all).unwrap(); assert_eq!(copy.teams[leaf.0].path, "all/leaf"); assert_eq!(org.teams[leaf.0].path, "all/eng/ops/web/leaf"); println!("all checks passed");}Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.
Show a solution
#![forbid(unsafe_code)]
use std::sync::atomic::{AtomicUsize, Ordering};
static CLONES: AtomicUsize = AtomicUsize::new(0);
#[derive(Clone, Copy, Debug, PartialEq)]struct Id(usize);
struct Team { name: String, size: u32, parent: Option<Id>, path: String,}
// Counts every clone of a team, so a "fix" that copies the teams shows up in the checks.impl Clone for Team { fn clone(&self) -> Team { CLONES.fetch_add(1, Ordering::SeqCst); Team { name: self.name.clone(), size: self.size, parent: self.parent, path: self.path.clone(), } }}
// All the teams live in one Vec; a team names its parent by Id, not by reference.#[derive(Clone)]struct Org { teams: Vec<Team>,}
impl Org { fn add(&mut self, name: &str, size: u32, parent: Option<Id>) -> Id { let path = match parent { Some(p) => format!("{}/{}", self.teams[p.0].path, name), None => name.to_string(), }; self.teams.push(Team { name: name.to_string(), size, parent, path, }); Id(self.teams.len() - 1) }
// The team's size plus the size of every team below it, at any depth. fn total(&self, id: Id) -> u32 { let mut sum = self.teams[id.0].size; for (i, team) in self.teams.iter().enumerate() { if team.parent == Some(id) { sum += self.total(Id(i)); } } sum }
// Set `path` to the parent's path, a slash and the team's name (a root's path is // its name), then do the same for every team below it. fn relabel(&mut self, id: Id) { let path = match self.teams[id.0].parent { Some(p) => format!("{}/{}", self.teams[p.0].path, self.teams[id.0].name), None => self.teams[id.0].name.clone(), }; self.teams[id.0].path = path; let below: Vec<usize> = (0..self.teams.len()) .filter(|&i| self.teams[i].parent == Some(id)) .collect(); for i in below { self.relabel(Id(i)); } }
// Move `id` (and everything below it) under `new_parent`, and relabel. // Err if that would make a team its own ancestor: `new_parent` is `id` or lies below it. fn reparent(&mut self, id: Id, new_parent: Id) -> Result<(), String> { let mut at = Some(new_parent); while let Some(p) = at { if p == id { return Err(format!("{} cannot go under itself", self.teams[id.0].name)); } at = self.teams[p.0].parent; } self.teams[id.0].parent = Some(new_parent); self.relabel(id); Ok(()) }}
fn main() { let mut org = Org { teams: Vec::new() }; let all = org.add("all", 1, None); let eng = org.add("eng", 10, Some(all)); let web = org.add("web", 4, Some(eng)); let ops = org.add("ops", 3, Some(all)); assert_eq!(org.total(web), 4); assert_eq!(org.total(eng), 14); assert_eq!(org.total(all), 18); assert_eq!(org.teams[web.0].path, "all/eng/web");
// Growing the Vec does not invalidate any Id. for i in 0..100 { org.add("temp", 0, Some(ops)); assert_eq!(org.total(ops), 3, "temp {i} has size 0"); } assert_eq!(org.teams[web.0].name, "web");
// Moving a team moves everything below it, and their paths follow. let leaf = org.add("leaf", 2, Some(web)); assert_eq!(org.reparent(web, ops), Ok(())); assert_eq!(org.total(eng), 10); assert_eq!(org.total(ops), 9); assert_eq!(org.total(all), 20); assert_eq!(org.teams[web.0].path, "all/ops/web"); assert_eq!(org.teams[leaf.0].path, "all/ops/web/leaf");
// `web` now has a smaller index than its parent `ops`: do not rely on the order. assert_eq!(org.reparent(ops, eng), Ok(())); assert_eq!(org.teams[ops.0].path, "all/eng/ops"); assert_eq!(org.teams[web.0].path, "all/eng/ops/web"); assert_eq!(org.teams[leaf.0].path, "all/eng/ops/web/leaf");
// A team cannot go under itself or under one of its own descendants. assert!(org.reparent(all, web).is_err()); assert!(org.reparent(eng, eng).is_err()); assert!(org.reparent(eng, leaf).is_err()); assert_eq!(org.total(all), 20, "a refused move changes nothing"); assert_eq!(org.teams[eng.0].path, "all/eng");
// The teams are moved, not copied. assert_eq!(CLONES.load(Ordering::SeqCst), 0, "do not clone the teams");
// A copy of the whole organisation is independent of the original. let mut copy = org.clone(); copy.reparent(leaf, all).unwrap(); assert_eq!(copy.teams[leaf.0].path, "all/leaf"); assert_eq!(org.teams[leaf.0].path, "all/eng/ops/web/leaf"); println!("all checks passed");}all checks passed
Read more
- The Rust Book: Understanding Ownership and References and Borrowing.
- The Rust Book: updating a hash map, for
entry. std::mem::take,replaceandOption::take.slice::split_at_mutandget_disjoint_mut.Vec::retainandHashMap::extract_if.- Reading compiler errors: every code here. Rust by Example: borrowing and lifetimes.
Learn Rust is an independent project, not affiliated with the Rust Project or the Rust Foundation. Code and text are under the MIT licence.Source on GitHub.