Skip to content
Learn Rust
Theme

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

Rustsrc/main.rsRun ↗
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);
}
$ cargo run
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))
}
$ go run main.go
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\"" } + "]")
}
$ kotlinc Main.kt -include-runtime -d main.jar && java -jar main.jar
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) + "]")
$ python3 main.py
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(", ") + "]");
$ tsc --strict --module nodenext --types node main.ts && node main.js
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(", ", "[", "]")));
}
}
$ javac Main.java && java Main
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}");
}
}
}
}
$ dotnet run
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");
}
$ gcc -Wall -Wextra main.c -o main && ./main
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";
}
$ g++ -std=c++20 -Wall -Wextra main.cpp -o main && ./main
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 of self. A field access such as self.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.
  • clone is 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".
Gotcha

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.
Rustsrc/main.rs✗ won't compile
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))
}
$ go run main.go✓ runs
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\"" } + "]")
}
$ kotlinc Main.kt -include-runtime -d main.jar && java -jar main.jar✓ runs
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) + "]")
$ python3 main.py✓ runs
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(", ") + "]");
$ tsc --strict --module nodenext --types node main.ts && node main.js✓ runs
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(", ", "[", "]")));
}
}
$ javac Main.java && java Main✓ runs
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}");
}
}
}
}
$ dotnet run✓ runs
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");
}
$ gcc -Wall -Wextra main.c -o main && ./main✓ runs
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";
}
$ g++ -std=c++20 -Wall -Wextra main.cpp -o main && ./main✓ runs
log: ["out of stock: bolt", "out of stock: washer"]
What does the compiler say? Guess first, then open.
$ cargo buildWhat is E0502?
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
What happens in Go

Go runs this and prints the log. range reads the slice once at the start, and note appends to a different slice.

What happens in Kotlin

Kotlin runs this and prints the log. note changes log, not the list the loop walks, so there is no ConcurrentModificationException.

What happens in Python

Python prints the log. note changes self.log, not the list being looped over.

What happens in TypeScript

Prints the log. note pushes to log, not to the array the loop walks.

What happens in Java

Java runs this and prints the log. note adds to log; the list being iterated is not changed, so no ConcurrentModificationException.

What happens in C#

C# runs this and prints the log. foreach walks items; Note changes Log, a different list.

What happens in C

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.

What happens in C++

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.

What Rust sees

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.

Why Rust says no

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.

Rust, fixedsrc/main.rsRun ↗
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);
}
$ cargo run
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 clone or Rc in one struct, added to quiet an error;
  • &mut self methods 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 String and 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)

Rustsrc/main.rs✗ won't compile
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());
}
$ cargo buildWhat is E0382?
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:

Rustsrc/main.rsRun ↗
// 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);
}
$ cargo run
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):

Rustsrc/main.rs✗ won't compile
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);
}
$ cargo buildWhat is E0499?
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:

Rustsrc/main.rsRun ↗
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());
}
$ cargo run
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:

Rustsrc/main.rsRun ↗
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);
}
$ cargo run
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:

Rustsrc/main.rs✗ won't compile
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);
}
$ cargo buildWhat is E0500?
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.

Rustsrc/main.rs✗ won't compile
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:?}");
}
$ cargo buildWhat is E0499?
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:

Rustsrc/main.rsRun ↗
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:?}");
}
$ cargo run
{"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:

Rustsrc/main.rsRun ↗
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);
}
$ cargo run
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)

Rustsrc/main.rs✗ won't compile
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}");
}
$ cargo buildWhat is E0505?
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
Rustsrc/main.rs✗ won't compile
fn main() {
let mut position = String::from("start");
let before = &position;
position = String::from("middle");
println!("moved from {before} to {position}");
}
$ cargo buildWhat is E0506?
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:

Rustsrc/main.rsRun ↗
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}");
}
$ cargo run
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:

Rustsrc/main.rs✗ won't compile
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());
}
$ cargo buildWhat is E0502?
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
Rustsrc/main.rsRun ↗
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()));
}
$ cargo run
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.

Rustsrc/main.rs✗ won't compile
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}");
}
$ cargo buildWhat is E0597?
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:

Rustsrc/main.rsRun ↗
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}");
}
$ cargo run
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:

Rustsrc/main.rs✗ won't compile
struct Dept {
name: String,
parent: Option<&Dept>,
}
fn main() {
let hq = Dept {
name: String::from("HQ"),
parent: None,
};
println!("{}", hq.name);
}
$ cargo buildWhat is E0106?
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>,
Rustsrc/main.rs✗ won't compile
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);
}
$ cargo buildWhat is E0506?
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:

Rustsrc/main.rs✗ won't compile
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);
}
$ cargo buildWhat is E0505?
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:

Rustsrc/main.rsRun ↗
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));
}
$ cargo run
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:

Rustsrc/main.rsRun ↗
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));
}
$ cargo run
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 items read-only and log writable is describing two types (the Log fix 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.
Rustsrc/main.rsRun ↗
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);
}
$ cargo run
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.

Starterexercises/low-stock.rs
#![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
One solutionexercises/low-stock.solution.rsRun ↗
#![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");
}
$ cargo run
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.

Starterexercises/ledger.rs
#![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
One solutionexercises/ledger.solution.rsRun ↗
#![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");
}
$ cargo run
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.

Starterexercises/org.rs
#![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
One solutionexercises/org.solution.rsRun ↗
#![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");
}
$ cargo run
all checks passed

Read more