Skip to content
Learn Rust
Theme

Mutable Borrows

Ownership · Lesson 19

&mut lends a value so it can be changed. The rule: while something holds a &mut, nothing else can use that value at all. Many readers, or one writer, never both.

Coming from Go&mut Vec<i32> is like your *[]int, except you don't write (*scores)[i]: indexing and loops look through the reference. What Go has no version of is the exclusivity: while a &mut exists, nothing else can read or write that value.

Coming from KotlinA &mut Vec<i32> parameter is like a MutableList<Int> parameter, but the caller has to write &mut scores, so the call shows what may change. A Kotlin List is a read-only view that another reference can still change under you. While a &mut is lent out, no other reference exists at all, not even to read.

Coming from PythonPython lets any reference change a list. Rust only allows it through &mut, which must be written on both the parameter and the call. Only one &mut can exist at a time, and nothing else can even read the list while it does.

Coming from TypeScriptAny reference to an array can change it in TypeScript, unless that reference is typed readonly, which restricts only that one reference. In Rust only a &mut can change it, you write it at the call (&mut scores), and while it exists nothing else can read or write the value.

Coming from Java&mut Vec<i32> is like passing a List<Integer> that the method may change, except the signature says so, and Rust allows only one such writer at a time with no readers alongside it. (A read-only view such as Collections.unmodifiableList exists, but it only restricts the reference you hold.) Java has references to element objects but not to a list slot, so the Rust fix for changing two elements looks like yours: scores[i] += 1, where you'd write scores.set(i, scores.get(i) + 1).

Coming from C#&mut is like a ref parameter or ref local, but exclusive: while one &mut exists, nothing else can read or write that value. C# lets two ref locals point at the same array slot; Rust doesn't, so use indexes or split_at_mut.

Coming from C&mut T is restrict made universal and checked. In C, restrict is a promise you keep yourself; Rust refuses &mut arr[i] and &mut arr[j] at once, even for different elements. (split_at_mut and slice patterns are the way to get two.)

Coming from C++&mut T is a non-const T&, with one extra rule: only one can be alive at a time, and no const references alongside it. Two int& into one vector are fine in C++ (until it grows), but rejected in Rust; use indexes or split_at_mut.

The quick comparison

Rustsrc/main.rsRun ↗
fn add_bonus(scores: &mut Vec<i32>, bonus: i32) {
// iter_mut() visits each number as a &mut, a reference that can change it
for s in scores.iter_mut() {
*s += bonus; // *s is the number that s points at
}
scores.push(0);
}
fn main() {
let mut scores = vec![10, 20]; // vec![...] makes a growable list
add_bonus(&mut scores, 5);
println!("{scores:?}");
}
$ cargo run
[15, 25, 0]
package main
import "fmt"
func addBonus(scores *[]int, bonus int) {
for i := range *scores {
(*scores)[i] += bonus
}
*scores = append(*scores, 0)
}
func main() {
scores := []int{10, 20}
addBonus(&scores, 5)
fmt.Println(scores)
}
$ go run main.go
[15 25 0]
fun addBonus(scores: MutableList<Int>, bonus: Int) {
for (i in scores.indices) {
scores[i] += bonus
}
scores.add(0)
}
fun main() {
val scores = mutableListOf(10, 20)
addBonus(scores, 5)
println(scores)
}
$ kotlinc Main.kt -include-runtime -d main.jar && java -jar main.jar
[15, 25, 0]
def add_bonus(scores: list[int], bonus: int) -> None:
for i in range(len(scores)):
scores[i] += bonus # lists are passed by reference, so this changes the caller's list
scores.append(0)
scores = [10, 20]
add_bonus(scores, 5)
print(scores)
$ python3 main.py
[15, 25, 0]
// arrays are passed by reference, so the function can change the caller's array
function addBonus(scores: number[], bonus: number): void {
for (let i = 0; i < scores.length; i++) {
scores[i] += bonus;
}
scores.push(0);
}
const scores = [10, 20];
addBonus(scores, 5);
console.log(scores);
$ tsc --strict --module nodenext --types node main.ts && node main.js
[ 15, 25, 0 ]
import java.util.ArrayList;
import java.util.List;
public class Main {
static void addBonus(List<Integer> scores, int bonus) {
scores.replaceAll(s -> s + bonus);
scores.add(0);
}
public static void main(String[] args) {
var scores = new ArrayList<>(List.of(10, 20));
addBonus(scores, 5);
System.out.println(scores);
}
}
$ javac Main.java && java Main
[15, 25, 0]
List<int> scores = [10, 20];
AddBonus(scores, 5);
Console.WriteLine($"[{string.Join(", ", scores)}]");
static void AddBonus(List<int> scores, int bonus)
{
for (var i = 0; i < scores.Count; i++)
{
scores[i] += bonus;
}
scores.Add(0);
}
$ dotnet run
[15, 25, 0]
#include <stdio.h>
#include <stdlib.h>
struct IntVec {
int *data;
size_t len, cap;
};
void push(struct IntVec *v, int x) {
if (v->len == v->cap) {
v->cap = v->cap ? v->cap * 2 : 4;
v->data = realloc(v->data, v->cap * sizeof(int));
}
v->data[v->len++] = x;
}
// a non-const pointer: this function may change the list
void add_bonus(struct IntVec *scores, int bonus) {
for (size_t i = 0; i < scores->len; i++) {
scores->data[i] += bonus;
}
push(scores, 0);
}
int main(void) {
struct IntVec scores = {0};
push(&scores, 10);
push(&scores, 20);
add_bonus(&scores, 5);
printf("[");
for (size_t i = 0; i < scores.len; i++) {
printf(i ? ", %d" : "%d", scores.data[i]);
}
printf("]\n");
free(scores.data);
return 0;
}
$ gcc -Wall -Wextra main.c -o main && ./main
[15, 25, 0]
#include <iostream>
#include <vector>
void add_bonus(std::vector<int>& scores, int bonus) { // non-const reference: may change it
for (int& s : scores) {
s += bonus;
}
scores.push_back(0);
}
int main() {
std::vector<int> scores{10, 20};
add_bonus(scores, 5);
std::cout << "[";
for (std::size_t i = 0; i < scores.size(); ++i) {
std::cout << (i ? ", " : "") << scores[i];
}
std::cout << "]\n";
}
$ g++ -std=c++20 -Wall -Wextra main.cpp -o main && ./main
[15, 25, 0]

What's different

  • &mut x lends x out to be changed. A plain & only looks. Because &mut is written at the call, you can see which calls change your data.
  • *r means "the value r points at". Method calls and printing look through a reference for you, so you mostly see * when assigning: *r += 1.
  • You need mut in two places: the variable (let mut scores) and the borrow (&mut scores). The first is at the declaration, the second at the call.
  • This one-writer rule is the main rule that prevents data races in safe Rust, together with the Send and Sync rules for threads (Send and Sync). It is the rule of a read-write lock (many readers or one writer), but checked while compiling.
Gotcha

Two writers into the same list

Looks fine: in Go, C#, C and C++ it runs fine. Rust won't compile it.

Fine in Go. Rust can't prove it's safe.No direct equivalent in Kotlin. Just a Rust rule.No direct equivalent in Python. Just a Rust rule.No direct equivalent in TypeScript. Just a Rust rule.No direct equivalent in Java. Just a Rust rule.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
fn main() {
let mut scores = vec![10, 20, 30];
let first = &mut scores[0];
let last = &mut scores[2];
*first += 1;
*last += 1;
println!("{scores:?}");
}
package main
import "fmt"
func main() {
scores := []int{10, 20, 30}
first := &scores[0]
last := &scores[2]
*first += 1
*last += 1
fmt.Println(scores)
}
$ go run main.go✓ runs
[11 20 31]
Kotlinno equivalent

Kotlin has no reference to a list slot (and an Int can't be changed), so you would write scores[0] += 1 and scores[2] += 1. That is also the simplest Rust fix.

Pythonno equivalent

Python has no reference to a list slot, only to the objects in it, so you would write scores[0] += 1 and scores[2] += 1. That is also the simplest Rust fix.

TypeScriptno equivalent

TypeScript has no reference to an array element. let first = scores[0]; first += 1 changes a copy of the number and leaves the array alone. You would write scores[0] += 1, which is also the simplest Rust fix.

Javano equivalent

Java has references to element objects but not to a list slot, and an Integer can't be changed, so you would write scores.set(0, scores.get(0) + 1). In Rust, scores[0] += 1 is the simplest fix.

int[] scores = [10, 20, 30];
ref int first = ref scores[0];
ref int last = ref scores[2];
first += 1;
last += 1;
Console.WriteLine($"[{string.Join(", ", scores)}]");
$ dotnet run✓ runs
[11, 20, 31]
#include <stdio.h>
int main(void) {
int scores[] = {10, 20, 30};
int *first = &scores[0];
int *last = &scores[2];
*first += 1;
*last += 1;
printf("[%d, %d, %d]\n", scores[0], scores[1], scores[2]);
return 0;
}
$ gcc -Wall -Wextra main.c -o main && ./main✓ runs
[11, 20, 31]
#include <iostream>
#include <vector>
int main() {
std::vector<int> scores{10, 20, 30};
int& first = scores[0];
int& last = scores[2];
first += 1;
last += 1;
std::cout << "[" << scores[0] << ", " << scores[1] << ", " << scores[2] << "]\n";
}
$ g++ -std=c++20 -Wall -Wextra main.cpp -o main && ./main✓ runs
[11, 20, 31]
What does the compiler say? Guess first, then open.
$ cargo buildWhat is E0499?
error[E0499]: cannot borrow `scores` as mutable more than once at a time
 --> src/main.rs:4:21
  |
3 |     let first = &mut scores[0];
  |                      ------ first mutable borrow occurs here
4 |     let last = &mut scores[2];
  |                     ^^^^^^ second mutable borrow occurs here
5 |     *first += 1;
  |     ----------- first borrow later used here
  |
  = help: use `.split_at_mut(position)` to obtain two mutable non-overlapping sub-slices
What happens in Go

Go runs this and prints [11 20 31]. Two pointers into the same slice are fine in Go, even when both write. (If two goroutines wrote through them without coordination, that would be a data race.)

What happens in Kotlin

Kotlin has no reference to a list slot (and an Int can't be changed), so you would write scores[0] += 1 and scores[2] += 1. That is also the simplest Rust fix.

What happens in Python

Python has no reference to a list slot, only to the objects in it, so you would write scores[0] += 1 and scores[2] += 1. That is also the simplest Rust fix.

What happens in TypeScript

TypeScript has no reference to an array element. let first = scores[0]; first += 1 changes a copy of the number and leaves the array alone. You would write scores[0] += 1, which is also the simplest Rust fix.

What happens in Java

Java has references to element objects but not to a list slot, and an Integer can't be changed, so you would write scores.set(0, scores.get(0) + 1). In Rust, scores[0] += 1 is the simplest fix.

What happens in C#

C# allows two ref locals into the same array and prints [11, 20, 31]. Nothing checks whether two refs point at the same slot.

What happens in C

C allows any number of pointers that write into the same array. This prints [11, 20, 31].

What happens in C++

Two int& into the same vector are fine in C++, so this prints [11, 20, 31]. (They would dangle if the vector grew.) Rust allows only one &mut at a time, and you would use split_at_mut or indexes.

What Rust sees

&mut scores[0] lends element 0 out to be changed (see the notes above). The borrow checker does not look at index values. It sees two &mut borrows of scores, both alive at the same time. (It doesn't track indexes in an array either: there the error names scores[_].)

Why Rust says no

This code is safe, but Rust can't prove it. Change 0 and 2 to variables i and j. When i == j, you have two writers to the same number. The compiler cannot tell that case from this one, so it refuses both. When you really need two parts at once, ask for pieces that cannot overlap.

The fix

Compiles and runs.

Rust, fixedsrc/main.rsRun ↗
fn main() {
let mut scores = vec![10, 20, 30];
scores[0] += 1; // simplest: one change at a time
scores[2] += 1;
// two at once: split into halves that can't overlap
let (left, right) = scores.split_at_mut(2);
left[0] += 1;
right[0] += 1;
println!("{scores:?}");
}
$ cargo run
[12, 20, 32]

The toolkit

Borrowing lent a value out to be read, with &. This lesson lends it out to be changed, with &mut. One rule governs it, and it explains many of the first errors you will meet. Outputs come from Linux on 64-bit ARM; where a fact depends on the platform or build mode, the text says so.

&mut: lend a value to be changed

A & reference only looks (a few types in Shared state are the exception). A &mut reference, a mutable reference, can change the value it points at. A function that changes its argument takes one:

Rustsrc/main.rsRun ↗
fn add_one(x: &mut i32) {
*x += 1;
println!("inside add_one: x = {x}");
}
fn add_one_to_copy(mut x: i32) -> i32 {
x += 1;
x
}
fn main() {
let mut n = 5;
add_one(&mut n);
println!("n after add_one: {n}");
let copy_result = add_one_to_copy(n);
println!("add_one_to_copy returned {copy_result}, n is still {n}");
}
$ cargo run
inside add_one: x = 6
n after add_one: 6
add_one_to_copy returned 7, n is still 6

*x means “the value x points at”. x is the reference and *x is the number, so *x += 1 changes the number. Printing and method calls look through a reference for you, so you mostly meet * on the left of an assignment.

add_one_to_copy is there for comparison: its mut x: i32 changes the function’s own copy, and the caller’s n stays 6. Only &mut reaches the caller’s variable.

mut appears in two places, and the compiler checks both. The variable must be let mut, and the borrow &mut:

Rustsrc/main.rs✗ won't compile
fn add_one(x: &mut i32) {
*x += 1;
}
fn main() {
let n = 5;
add_one(&mut n);
println!("n: {n}");
}
$ cargo buildWhat is E0596?
error[E0596]: cannot borrow `n` as mutable, as it is not declared as mutable
 --> src/main.rs:7:13
  |
7 |     add_one(&mut n);
  |             ^^^^^^ cannot borrow as mutable
  |
help: consider changing this to be mutable
  |
6 |     let mut n = 5;
  |         +++

The suggestion (let mut n) is the fix. The call add_one(&mut n) also tells you, without opening add_one, that n may change.

A function that only has a & can’t push to its list. items.push(0) is a method call, a function attached to a type (Methods), and push needs a &mut:

Rustsrc/main.rs✗ won't compile
fn add_item(items: &Vec<i32>) {
items.push(0);
}
fn main() {
let mut items = vec![1, 2, 3];
add_item(&items);
println!("items: {items:?}");
}
$ cargo buildWhat is E0596?
error[E0596]: cannot borrow `*items` as mutable, as it is behind a `&` reference
 --> src/main.rs:2:5
  |
2 |     items.push(0);
  |     ^^^^^ `items` is a `&` reference, so it cannot be borrowed as mutable
  |
help: consider changing this to be a mutable reference
  |
1 | fn add_item(items: &mut Vec<i32>) {
  |                     +++

Same E0596, same reason. The fix is &mut Vec<i32> in the signature and add_item(&mut items) in the caller.

The rule: one writer, or any number of readers

At any moment, a value can be borrowed in one of two ways:

  • any number of & borrows (readers), or
  • exactly one &mut borrow (the writer).

Never both. While the writer exists, nobody else can use the value, not even the owner. Each cell is what happens while the borrow in the row is alive:

While this is alive… another & a &mut the owner reads the owner assigns
a & fine E0502 fine E0506
a &mut E0502 E0499 E0502 or E0503 E0506

Many readers are fine because nobody can change anything under them:

Rustsrc/main.rsRun ↗
fn main() {
let n = 10;
let a = &n;
let b = &n;
let c = a; // a shared reference can be copied
println!("a: {a}, b: {b}, c: {c}, n: {n}");
}
$ cargo run
a: 10, b: 10, c: 10, n: 10

Two writers is E0499:

Rustsrc/main.rs✗ won't compile
fn main() {
let mut n = 10;
let a = &mut n;
let b = &mut n;
*a += 1;
*b += 1;
println!("n: {n}");
}
$ cargo buildWhat is E0499?
error[E0499]: cannot borrow `n` as mutable more than once at a time
 --> src/main.rs:4:13
  |
3 |     let a = &mut n;
  |             ------ first mutable borrow occurs here
4 |     let b = &mut n;
  |             ^^^^^^ second mutable borrow occurs here
5 |     *a += 1;
  |     ------- first borrow later used here

A reader and a writer is E0502:

Rustsrc/main.rs✗ won't compile
fn main() {
let mut n = 10;
let reader = &n;
let writer = &mut n;
*writer += 1;
println!("reader sees {reader}");
}
$ cargo buildWhat is E0502?
error[E0502]: cannot borrow `n` as mutable because it is also borrowed as immutable
 --> src/main.rs:4:18
  |
3 |     let reader = &n;
  |                  -- immutable borrow occurs here
4 |     let writer = &mut n;
  |                  ^^^^^^ mutable borrow occurs here
5 |     *writer += 1;
6 |     println!("reader sees {reader}");
  |                            ------ immutable borrow later used here

The owner is not exempt. Assigning is E0506 while either kind is alive. Reading while a writer is alive is E0502 when the read takes a borrow (a method call, println!) and E0503 when it copies a number out:

Rustsrc/main.rs✗ won't compile
fn assign_while_read() {
let mut n = 10;
let reader = &n;
n = 20; // the owner assigns while a reader is alive
println!("reader sees {reader}");
}
fn assign_while_written() {
let mut n = 10;
let writer = &mut n;
n = 20; // the owner assigns while a writer is alive
*writer += 1;
}
fn read_while_written() {
let mut n = 10;
let writer = &mut n;
let copy = n; // the owner reads while a writer is alive
*writer += 1;
println!("copy: {copy}");
}
fn main() {
assign_while_read();
assign_while_written();
read_while_written();
}
$ cargo buildWhat is E0506?
error[E0506]: cannot assign to `n` because it is borrowed
 --> src/main.rs:4:5
  |
3 |     let reader = &n;
  |                  -- `n` is borrowed here
4 |     n = 20; // the owner assigns while a reader is alive
  |     ^^^^^^ `n` is assigned to here but it was already borrowed
5 |     println!("reader sees {reader}");
  |                            ------ borrow later used here

error[E0506]: cannot assign to `n` because it is borrowed
  --> src/main.rs:11:5
   |
10 |     let writer = &mut n;
   |                  ------ `n` is borrowed here
11 |     n = 20; // the owner assigns while a writer is alive
   |     ^^^^^^ `n` is assigned to here but it was already borrowed
12 |     *writer += 1;
   |     ------------ borrow later used here

error[E0503]: cannot use `n` because it was mutably borrowed
  --> src/main.rs:18:16
   |
17 |     let writer = &mut n;
   |                  ------ `n` is borrowed here
18 |     let copy = n; // the owner reads while a writer is alive
   |                ^ use of borrowed `n`
19 |     *writer += 1;
   |     ------------ borrow later used here

Some errors have detailed explanations: E0503, E0506.

Each error points at three places: the first borrow, the clashing one, and a later use of the first, which keeps it alive across the clash. Without that later use it would compile.

A borrow ends at its last use

A borrow doesn’t last until the end of the block. It lasts until the last time the reference is used. After that the value is free again:

Rustsrc/main.rsRun ↗
fn main() {
let mut scores = vec![10, 20];
let writer = &mut scores;
writer.push(30); // the last use of `writer`: its borrow ends here
println!("scores: {scores:?}");
scores.push(40); // this borrows `scores` mutably for the call
println!("scores: {scores:?}");
}
$ cargo run
scores: [10, 20, 30]
scores: [10, 20, 30, 40]

writer was last used on its push line, so the borrow was over by the next line. Move the use of writer below the println! and the borrow is still alive at the print:

Rustsrc/main.rs✗ won't compile
fn main() {
let mut scores = vec![10, 20];
let writer = &mut scores;
println!("scores: {scores:?}");
writer.push(30); // `writer` is used again, so its borrow was still alive above
}
$ cargo buildWhat is E0502?
error[E0502]: cannot borrow `scores` as immutable because it is also borrowed as mutable
 --> src/main.rs:4:24
  |
3 |     let writer = &mut scores;
  |                  ----------- mutable borrow occurs here
4 |     println!("scores: {scores:?}");
  |                        ^^^^^^ immutable borrow occurs here
5 |     writer.push(30); // `writer` is used again, so its borrow was still alive above
  |     ------ mutable borrow later used here

The message says it: “mutable borrow later used here”. scores.push(40) borrows scores as &mut for you, which is why scores has to be let mut though you never typed &mut.

Changing a list in place

for p in &mut prices visits each element as a &mut. Indexing works too: prices[i] *= 2 changes one element. The Vec has to be let mut:

Rustsrc/main.rsRun ↗
fn main() {
let mut prices = vec![100, 250, 80];
for p in &mut prices {
*p += 5;
}
println!("after for p in &mut prices: {prices:?}");
for i in 0..prices.len() {
prices[i] *= 2;
}
println!("after prices[i] *= 2: {prices:?}");
prices[1] -= 50;
println!("after prices[1] -= 50: {prices:?}");
}
$ cargo run
after for p in &mut prices: [105, 255, 85]
after prices[i] *= 2: [210, 510, 170]
after prices[1] -= 50: [210, 460, 170]

The rule shows up in loops. A for loop over &v keeps v borrowed until the loop ends, so adding to the list inside it is a writer meeting a reader:

Rustsrc/main.rs✗ won't compile
fn main() {
let mut v = vec![1, 2, 3];
for x in &v {
if *x == 2 {
v.push(20);
}
}
println!("v: {v:?}");
}
$ cargo buildWhat is E0502?
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
 --> src/main.rs:5:13
  |
3 |       for x in &v {
  |       -        --
  |       |        |
  |       |        immutable borrow occurs here
  |  _____|        immutable borrow later used here
  | |
4 | |         if *x == 2 {
5 | |             v.push(20);
  | |             ^^^^^^^^^^ mutable borrow occurs here
6 | |         }
7 | |     }
  | |_____- this for loop borrows `v` immutably, preventing mutation within its body
  |
  = help: consider using an index-based loop instead, or collecting modifications into a separate collection

The loop is the reader, push is the writer. Two ways out, both from the compiler’s help: line:

Rustsrc/main.rsRun ↗
fn main() {
// Fix 1: loop over the positions that exist now, and look each one up.
let mut v = vec![1, 2, 3];
let count = v.len();
for i in 0..count {
if v[i] == 2 {
v.push(20);
}
}
println!("index loop: {v:?}");
// Fix 2: collect what to add in a second list, then add it after the loop.
let mut w = vec![1, 2, 3];
let mut extra = Vec::new();
for x in &w {
if *x == 2 {
extra.push(20);
}
}
w.extend(extra);
println!("second list: {w:?}");
}
$ cargo run
index loop: [1, 2, 3, 20]
second list: [1, 2, 3, 20]

Loop over the positions that exist now, or collect what to add in a second list and add it after the loop. Ownership and Moves has more ways out.

When the rule is in the way

The rule refuses some safe programs. These are the usual ways round it. Stuck on an error you can’t fix? See Working with the Borrow Checker. Otherwise skip to Going deeper and come back when the compiler stops you.

Handing a &mut on

A &mut can’t be copied, because a copy would be a second writer. let second = first; moves it, like a String:

Rustsrc/main.rs✗ won't compile
fn main() {
let mut n = 0;
let first = &mut n;
let second = first; // moves the `&mut`, like moving a String
*first += 1;
*second += 1;
println!("n: {n}");
}
$ cargo buildWhat is E0382?
error[E0382]: use of moved value: `first`
 --> src/main.rs:5:5
  |
3 |     let first = &mut n;
  |         ----- move occurs because `first` has type `&mut i32`, which does not implement the `Copy` trait
4 |     let second = first; // moves the `&mut`, like moving a String
  |                  ----- value moved here
5 |     *first += 1;
  |     ^^^^^^^^^^^ value used here after move

(A & is copied freely, as readers.rs did.) So you might expect that passing a &mut to a function uses it up. It doesn’t:

Rustsrc/main.rsRun ↗
fn add_one(x: &mut i32) {
*x += 1;
}
fn main() {
let mut n = 0;
let r = &mut n;
add_one(r); // `r` is lent for the call, not used up
add_one(&mut *r); // the same thing, written out
*r += 10;
println!("n: {n}");
}
$ cargo run
n: 12

When the parameter is known to be a &mut, the compiler lends r for the call: a reborrow, as if you’d written &mut *r. The loan lasts longer if the function returns a reference taken from it (Returning references). A generic parameter (fn f<T>(t: T)) has no known &mut to reborrow, so it moves r instead.

Reading while a push is on its way

Rustsrc/main.rsRun ↗
fn main() {
let mut v = vec![1, 2];
v.push(v.len()); // reads v while the push is on its way
println!("after v.push(v.len()): {v:?}");
let mut w = vec![5, 6];
w.push(w[0]);
println!("after w.push(w[0]): {w:?}");
}
$ cargo run
after v.push(v.len()): [1, 2, 2]
after w.push(w[0]):    [5, 6, 5]

v.push(v.len()) reads v while a &mut v is on its way. It works because the &mut only becomes active when push runs, after its arguments are worked out: a two-phase borrow. Until then only reads are allowed, and only for borrows the compiler takes for you. A &mut v you write is taken first, and an argument that changes v is refused:

Rustsrc/main.rs✗ won't compile
fn add(v: &mut Vec<usize>, n: usize) {
v.push(n);
}
fn bump(v: &mut Vec<usize>) -> usize {
v[0] += 1;
v[0]
}
fn explicit_borrow_first() {
let mut v = vec![1];
add(&mut v, v.len()); // the `&mut v` you write is taken first
println!("v: {v:?}");
}
fn argument_changes_the_list() {
let mut v = vec![1];
v.push(bump(&mut v)); // an argument that changes `v` is not just a read
println!("v: {v:?}");
}
fn main() {
explicit_borrow_first();
argument_changes_the_list();
}
$ cargo buildWhat is E0502?
error[E0502]: cannot borrow `v` as immutable because it is also borrowed as mutable
  --> src/main.rs:12:17
   |
12 |     add(&mut v, v.len()); // the `&mut v` you write is taken first
   |     --- ------  ^ immutable borrow occurs here
   |     |   |
   |     |   mutable borrow occurs here
   |     mutable borrow later used by call

error[E0499]: cannot borrow `v` as mutable more than once at a time
  --> src/main.rs:18:17
   |
18 |     v.push(bump(&mut v)); // an argument that changes `v` is not just a read
   |     - ----      ^^^^^^ second mutable borrow occurs here
   |     | |
   |     | first borrow later used by call
   |     first mutable borrow occurs here

Some errors have detailed explanations: E0499, E0502.

Swapping and taking

A &mut lets you change the value, but not take it. The caller still owns it and will drop it, so moving it out would drop it twice:

Rustsrc/main.rs✗ won't compile
fn take_name(name: &mut String) -> String {
*name
}
fn main() {
let mut name = String::from("Ada");
let taken = take_name(&mut name);
println!("taken: {taken}");
}
$ cargo buildWhat is E0507?
error[E0507]: cannot move out of `*name` which is behind a mutable reference
 --> src/main.rs:2:5
  |
2 |     *name
  |     ^^^^^ move occurs because `*name` has type `String`, which does not implement the `Copy` trait
  |
help: consider cloning the value if the performance cost is acceptable
  |
2 -     *name
2 +     name.clone()

This is E0507 from Ownership and Moves. What you can do is put something else in its place, with three functions from std::mem:

  • mem::swap(&mut a, &mut b) exchanges two values. (mem::swap(&mut x, &mut x) is refused as two writers.)
  • mem::replace(&mut x, new) puts new in x and returns the old value.
  • mem::take(&mut x) is replace with the type’s default value ("", an empty Vec, 0).
Rustsrc/main.rsRun ↗
use std::mem;
fn main() {
let mut a = String::from("left");
let mut b = String::from("right");
mem::swap(&mut a, &mut b);
println!("swap: a = {a}, b = {b}");
let mut title = String::from("draft");
let old = mem::replace(&mut title, String::from("final"));
println!("replace: old = {old}, title = {title}");
let mut log = vec![1, 2, 3];
let taken = mem::take(&mut log);
println!("take: taken = {taken:?}, log = {log:?}");
}
$ cargo run
swap:    a = right, b = left
replace: old = draft, title = final
take:    taken = [1, 2, 3], log = []

They are most useful inside a function that only has a &mut:

Rustsrc/main.rsRun ↗
use std::mem;
// `log` is only borrowed, so we can't move it out, but we can leave an empty Vec behind.
fn drain_log(log: &mut Vec<String>) -> Vec<String> {
mem::take(log)
}
fn main() {
let mut log = vec![String::from("started"), String::from("stopped")];
let text_before = log[0].as_ptr();
let all = drain_log(&mut log);
println!("all: {all:?}");
println!("log now: {log:?}, length {}", log.len());
println!("same text, not a copy: {}", all[0].as_ptr() == text_before);
log.push(String::from("started again")); // the emptied Vec still works
println!("log after push: {log:?}");
}
$ cargo run
all: ["started", "stopped"]
log now: [], length 0
same text, not a copy: true
log after push: ["started again"]

drain_log returns the whole list and leaves an empty one behind. The strings weren’t copied: the third line shows the first text at the same address as before. Swapping a String or Vec exchanges its small header and leaves the text where it is (the stack and the heap). take needs a type with a default value.

Changing two parts at once

Fields of a struct (a type with named fields; Structs) are separate places, and the compiler knows it when you name them:

Rustsrc/main.rsRun ↗
struct Stats {
wins: u32,
losses: u32,
}
fn main() {
let mut s = Stats { wins: 3, losses: 1 };
let w = &mut s.wins;
let l = &mut s.losses; // a different field: no overlap
*w += 1;
*l += 1;
println!("wins: {}, losses: {}", s.wins, s.losses);
}
$ cargo run
wins: 4, losses: 2

That works only because both borrows name a field. Pass the whole struct to a function and it could touch any field:

Rustsrc/main.rs✗ won't compile
struct Stats {
wins: u32,
losses: u32,
}
fn reset(s: &mut Stats) {
s.wins = 0;
s.losses = 0;
}
fn main() {
let mut s = Stats { wins: 3, losses: 1 };
let w = &mut s.wins;
reset(&mut s); // `reset` could touch any field, including `wins`
*w += 1;
println!("wins: {}, losses: {}", s.wins, s.losses);
}
$ cargo buildWhat is E0499?
error[E0499]: cannot borrow `s` as mutable more than once at a time
  --> src/main.rs:15:11
   |
14 |     let w = &mut s.wins;
   |             ----------- first mutable borrow occurs here
15 |     reset(&mut s); // `reset` could touch any field, including `wins`
   |           ^^^^^^ second mutable borrow occurs here
16 |     *w += 1;
   |     ------- first borrow later used here

Elements of a Vec are harder. The compiler doesn’t look at index values, so &mut v[i] and &mut v[j] both borrow the whole Vec. It can’t know that i and j differ:

Rustsrc/main.rs✗ won't compile
fn main() {
let mut v = vec![10, 20, 30];
let (i, j) = (0, 2);
let a = &mut v[i];
let b = &mut v[j];
std::mem::swap(a, b);
println!("v: {v:?}");
}
$ cargo buildWhat is E0499?
error[E0499]: cannot borrow `v` as mutable more than once at a time
 --> src/main.rs:6:18
  |
5 |     let a = &mut v[i];
  |                  - first mutable borrow occurs here
6 |     let b = &mut v[j];
  |                  ^ second mutable borrow occurs here
7 |     std::mem::swap(a, b);
  |                    - first borrow later used here
  |
  = help: use `.split_at_mut(position)` to obtain two mutable non-overlapping sub-slices

This is the gotcha at the top of the page. Usually you don’t need two references at all:

Rustsrc/main.rsRun ↗
fn main() {
let mut v = vec![10, 20, 30, 40];
// 1. Swap two elements: the Vec does it for you.
v.swap(0, 3);
println!("after v.swap(0, 3): {v:?}");
// 2. Numbers are copied out of the Vec, so read first and write after.
let (i, j) = (1, 2);
let moved = v[i];
v[i] = 0;
v[j] += moved;
println!("after moving v[1] into v[2]: {v:?}");
// 3. split_at_mut gives two halves that cannot overlap.
let (left, right) = v.split_at_mut(2);
left[0] += right[1];
right[0] += 1;
println!("after split_at_mut(2): {v:?}");
}
$ cargo run
after v.swap(0, 3): [40, 20, 30, 10]
after moving v[1] into v[2]: [40, 0, 50, 10]
after split_at_mut(2): [50, 0, 51, 10]
You want Use
to swap two elements v.swap(i, j)
to combine two numbers read the numbers out (they’re copied), then write
a &mut into each of two elements v.split_at_mut(mid): two non-overlapping halves

split_at_mut(2) splits a list into left, the elements before position 2, and right, the rest. The halves are slices (&mut [i32], a borrowed view of part of a list), and right[0] is v[2]. No element is in both halves, so there is no i == j to worry about. It panics if mid is past the end.

Going deeper

Why one writer or many readers?

The rule can feel too strict for one thread and one list. It targets a problem every language with changeable data meets: aliasing plus mutation. Aliasing means two names for the same data. If both can read and one can write, the reader can see the data change under its feet, or follow a pointer to something that no longer exists. Rust forbids the combination. Other languages tolerate it and deal with the consequences, as the rest of this page shows.

Two arguments that are the same thing

Rustsrc/main.rs✗ won't compile
fn add_twice(total: &mut i32, amount: &i32) {
*total += *amount;
*total += *amount;
}
fn main() {
let mut x = 1;
add_twice(&mut x, &x);
println!("x: {x}");
}
$ cargo buildWhat is E0502?
error[E0502]: cannot borrow `x` as immutable because it is also borrowed as mutable
 --> src/main.rs:8:23
  |
8 |     add_twice(&mut x, &x);
  |     --------- ------  ^^ immutable borrow occurs here
  |     |         |
  |     |         mutable borrow occurs here
  |     mutable borrow later used by call

Rust refuses the call because total would be written while amount reads the same number. That guarantees *amount can’t change when *total does, and the compiler may rely on it.

A reference into a list that moves

A Vec keeps its elements in one block on the heap. When a push doesn’t fit, it asks the allocator for a bigger block. If the block can’t be extended in place, the elements are moved to a new one and the old one is freed, leaving a reference to an element pointing at freed memory:

let mut v = vec![1, 2, 3, 4]; let first = &v[0];
STACKHEAPv: Vec<i32>ptrlen 4cap 4first: &i32address of v[0]v's buffer1234
Both v and first lead to the same buffer. So far this is fine, and the compiler accepts it.
v.push(5); // what would happen if the compiler allowed it
STACKHEAPv: Vec<i32>ptrlen 5cap 8first: &i32 (dangling)address of v[0]points at freed memoryv's new buffer12345old buffer (freed)1234
The push needed room for a fifth element, so Vec moved the elements to a bigger buffer and freed the old one. first still holds the old address. Rust refuses this program, because it is a reader (first) meeting a writer (push).

This program can’t hold a reference across the push, but it can compare as_ptr() (the buffer’s address) before and after, as true or false, because the address itself changes from run to run:

Rustsrc/main.rsRun ↗
fn push_and_report(label: &str, mut v: Vec<i32>) {
let before = v.as_ptr();
let capacity_before = v.capacity();
v.push(5);
println!(
"{label} capacity {capacity_before} -> {}, buffer moved: {}",
v.capacity(),
v.as_ptr() != before
);
}
fn main() {
let alone = vec![1, 2, 3, 4];
push_and_report("no neighbour:", alone);
let crowded = vec![1, 2, 3, 4];
let _neighbour = vec![0u8; 16]; // another allocation, probably right after `crowded`'s
push_and_report("a neighbour: ", crowded);
}
$ cargo run
no neighbour: capacity 4 -> 8, buffer moved: false
a neighbour:  capacity 4 -> 8, buffer moved: true

Capacity went from 4 to 8 both times, but only the second Vec got a new buffer. In the first the buffer stayed put, probably because nothing sat after it, so the allocator could extend it in place. Whether a push moves the elements depends on the allocator, which a program can’t know in advance, so the compiler treats every push as if it might. This is a debug build; with rustc -O the unused _neighbour is removed and both stay put. The capacity numbers are what this version of Vec does, not a rule.

Changing a list while looping over it

Rust refuses it (loop-push.fail.rs above). Each row names its test; the program for your language follows.

Language Test What happened
Rust add inside for x in &v does not compile (E0502)
Java, Kotlin add 99 when the loop sees 2 ConcurrentModificationException
Java remove 3 from [1, 2, 3, 4] when the loop sees 3 no exception; the loop skips 4
C# add 99 when the loop sees 2 InvalidOperationException
Python remove 3 from [1, 2, 3, 4] when the loop sees 3 no error; the loop skips 4
TypeScript same as Python no error; the loop skips 4
Go append 99 when range sees 2 no error; range never reaches 99
C++ push_back when a full vector’s loop sees 2 AddressSanitizer: heap-use-after-free

What the rule buys

  • No data races in safe Rust. A data race is two threads using the same memory at the same time, with at least one writing and nothing coordinating them. “One writer or many readers” is the main rule that prevents it, together with Send and Sync (traits that say which types may cross threads; Send and Sync). Shared state shows the tools for sharing on purpose.
  • A signature that says a lot. &T, &mut T and T usually say whether a function reads, changes or takes its argument. (Interior-mutable types change through a &; a Copy type isn’t consumed.)
  • Optimisation the compiler can trust. If a &mut is the only way to reach a value, the compiler can assume nothing else changes it. With rustc -O --emit=llvm-ir a &mut i32 parameter comes out marked noalias for LLVM, the compiler backend (Rustonomicon). Whether that speeds up a given program depends on the program; this lesson claims no number.

The rule has a price: it refuses safe programs, like two &mut to elements 0 and 2, which never overlap. You work round it as above. Some designs don’t fit at all, such as two objects that both hold a mutable pointer to a third. For those, some types change data through a shared &. Cell only copies values in and out, so it can’t clash; RefCell and Mutex check the rule while the program runs (RefCell in Smart pointers, Mutex in Shared state).

Practice

Try it

Open each exercise in the Playground, replace the todo!(), and run it until it prints all checks passed. Some checks compare text addresses with as_ptr(), so an answer that copies Strings fails.

Swap the ends

Swap the first and last element of a list of Strings, and leave lists with fewer than two elements as they are. You can’t move a String out of an index or borrow two elements mutably, but the Vec has a method for this. Mind the empty list: len - 1 on zero underflows (a panic in a debug build).

Starterexercises/swap-ends.rs
/// Swap the first and the last element. A list with fewer than two
/// elements is left as it is.
fn swap_ends(words: &mut Vec<String>) {
todo!("words.swap(i, j) swaps two elements; check the length first so an empty list is safe")
}
fn main() {
let mut words = vec![
String::from("first"),
String::from("second"),
String::from("third"),
String::from("last"),
];
let first_text = words[0].as_ptr();
let last_text = words[3].as_ptr();
swap_ends(&mut words);
assert_eq!(words, ["last", "second", "third", "first"]);
assert_eq!(
words[0].as_ptr(),
last_text,
"swap the Strings, do not copy them"
);
assert_eq!(
words[3].as_ptr(),
first_text,
"swap the Strings, do not copy them"
);
let mut odd = vec![String::from("a"), String::from("b"), String::from("c")];
swap_ends(&mut odd);
assert_eq!(odd, ["c", "b", "a"]);
let mut two = vec![String::from("x"), String::from("y")];
swap_ends(&mut two);
assert_eq!(two, ["y", "x"]);
let mut one = vec![String::from("only")];
swap_ends(&mut one);
assert_eq!(one, ["only"]);
let mut none: Vec<String> = Vec::new();
swap_ends(&mut none);
assert!(none.is_empty());
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/swap-ends.solution.rsRun ↗
/// Swap the first and the last element. A list with fewer than two
/// elements is left as it is.
fn swap_ends(words: &mut Vec<String>) {
let len = words.len();
if len >= 2 {
// `words[0]` and `words[len - 1]` can't both be borrowed mutably at once,
// and a String can't be moved out of an index. `swap` does both jobs.
words.swap(0, len - 1);
}
}
fn main() {
let mut words = vec![
String::from("first"),
String::from("second"),
String::from("third"),
String::from("last"),
];
let first_text = words[0].as_ptr();
let last_text = words[3].as_ptr();
swap_ends(&mut words);
assert_eq!(words, ["last", "second", "third", "first"]);
assert_eq!(
words[0].as_ptr(),
last_text,
"swap the Strings, do not copy them"
);
assert_eq!(
words[3].as_ptr(),
first_text,
"swap the Strings, do not copy them"
);
let mut odd = vec![String::from("a"), String::from("b"), String::from("c")];
swap_ends(&mut odd);
assert_eq!(odd, ["c", "b", "a"]);
let mut two = vec![String::from("x"), String::from("y")];
swap_ends(&mut two);
assert_eq!(two, ["y", "x"]);
let mut one = vec![String::from("only")];
swap_ends(&mut one);
assert_eq!(one, ["only"]);
let mut none: Vec<String> = Vec::new();
swap_ends(&mut none);
assert!(none.is_empty());
println!("all checks passed");
}
$ cargo run
all checks passed

Move points

Write transfer, which moves up to amount points from one &mut u32 to another and returns how many moved. main calls it with two fields of one struct. Then write transfer_in_list for two positions of a list, where &mut scores[i] and &mut scores[j] won’t compile. If i == j, nothing moves.

Starterexercises/move-points.rs
struct Team {
red: u32,
blue: u32,
}
/// Move up to `amount` points from `from` to `to`. Return how many moved:
/// fewer than `amount` when `from` doesn't have that many.
fn transfer(from: &mut u32, to: &mut u32, amount: u32) -> u32 {
todo!("work out how many can move, then subtract from *from and add to *to")
}
/// The same, between two positions `i` and `j` of a list of scores.
/// If `i` and `j` are the same position, nothing moves: return 0.
fn transfer_in_list(scores: &mut Vec<u32>, i: usize, j: usize, amount: u32) -> u32 {
todo!("two &mut scores[..] at once won't compile: read the number, then write both")
}
fn main() {
let mut team = Team { red: 5, blue: 1 };
// Two different fields of one struct: two `&mut` at once is fine.
let moved = transfer(&mut team.red, &mut team.blue, 3);
assert_eq!((moved, team.red, team.blue), (3, 2, 4));
// Asking for more than red has moves only what is there.
let moved = transfer(&mut team.red, &mut team.blue, 10);
assert_eq!((moved, team.red, team.blue), (2, 0, 6));
// Nothing left to move.
let moved = transfer(&mut team.red, &mut team.blue, 1);
assert_eq!((moved, team.red, team.blue), (0, 0, 6));
let mut scores = vec![4, 0, 7];
assert_eq!(transfer_in_list(&mut scores, 2, 1, 5), 5);
assert_eq!(scores, [4, 5, 2]);
assert_eq!(transfer_in_list(&mut scores, 2, 0, 9), 2);
assert_eq!(scores, [6, 5, 0]);
// The same position twice: nothing moves.
assert_eq!(transfer_in_list(&mut scores, 1, 1, 3), 0);
assert_eq!(scores, [6, 5, 0]);
let mut single = vec![9];
assert_eq!(transfer_in_list(&mut single, 0, 0, 4), 0);
assert_eq!(single, [9]);
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/move-points.solution.rsRun ↗
struct Team {
red: u32,
blue: u32,
}
/// Move up to `amount` points from `from` to `to`. Return how many moved:
/// fewer than `amount` when `from` doesn't have that many.
fn transfer(from: &mut u32, to: &mut u32, amount: u32) -> u32 {
let moved = if *from < amount { *from } else { amount };
*from -= moved;
*to += moved;
moved
}
/// The same, between two positions `i` and `j` of a list of scores.
/// If `i` and `j` are the same position, nothing moves: return 0.
fn transfer_in_list(scores: &mut Vec<u32>, i: usize, j: usize, amount: u32) -> u32 {
if i == j {
return 0;
}
// Numbers are copied out of the list, so no two borrows overlap.
let moved = if scores[i] < amount {
scores[i]
} else {
amount
};
scores[i] -= moved;
scores[j] += moved;
moved
}
fn main() {
let mut team = Team { red: 5, blue: 1 };
// Two different fields of one struct: two `&mut` at once is fine.
let moved = transfer(&mut team.red, &mut team.blue, 3);
assert_eq!((moved, team.red, team.blue), (3, 2, 4));
// Asking for more than red has moves only what is there.
let moved = transfer(&mut team.red, &mut team.blue, 10);
assert_eq!((moved, team.red, team.blue), (2, 0, 6));
// Nothing left to move.
let moved = transfer(&mut team.red, &mut team.blue, 1);
assert_eq!((moved, team.red, team.blue), (0, 0, 6));
let mut scores = vec![4, 0, 7];
assert_eq!(transfer_in_list(&mut scores, 2, 1, 5), 5);
assert_eq!(scores, [4, 5, 2]);
assert_eq!(transfer_in_list(&mut scores, 2, 0, 9), 2);
assert_eq!(scores, [6, 5, 0]);
// The same position twice: nothing moves.
assert_eq!(transfer_in_list(&mut scores, 1, 1, 3), 0);
assert_eq!(scores, [6, 5, 0]);
let mut single = vec![9];
assert_eq!(transfer_in_list(&mut single, 0, 0, 4), 0);
assert_eq!(single, [9]);
println!("all checks passed");
}
$ cargo run
all checks passed

Rename and drain

With only a &mut Profile, return the old name while storing a new one, and return all the tags while leaving an empty list behind. Both come from swapping and taking, and neither may copy a String.

Starterexercises/rename-and-drain.rs
struct Profile {
name: String,
tags: Vec<String>,
}
/// Give the profile a new name and return the old one.
fn rename(p: &mut Profile, new_name: String) -> String {
todo!("std::mem::replace puts a new value in and gives the old one back")
}
/// Remove every tag and return them. Afterwards `p.tags` is an empty Vec.
fn take_tags(p: &mut Profile) -> Vec<String> {
todo!("std::mem::take leaves an empty value behind")
}
fn main() {
let mut p = Profile {
name: String::from("Ada"),
tags: vec![String::from("admin"), String::from("beta")],
};
let ada_text = p.name.as_ptr();
let new_name = String::from("Grace");
let grace_text = new_name.as_ptr();
let old = rename(&mut p, new_name);
assert_eq!(old, "Ada");
assert_eq!(
old.as_ptr(),
ada_text,
"return the old String, do not copy it"
);
assert_eq!(p.name, "Grace");
assert_eq!(
p.name.as_ptr(),
grace_text,
"store the new String, do not copy it"
);
let admin_text = p.tags[0].as_ptr();
let tags = take_tags(&mut p);
assert_eq!(tags, ["admin", "beta"]);
assert_eq!(
tags[0].as_ptr(),
admin_text,
"hand over the tags, do not copy them"
);
assert!(p.tags.is_empty());
// Taking from an empty list is fine, and the profile still works.
assert!(take_tags(&mut p).is_empty());
p.tags.push(String::from("new"));
assert_eq!(p.tags, ["new"]);
println!("{}: all checks passed", p.name);
}

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/rename-and-drain.solution.rsRun ↗
use std::mem;
struct Profile {
name: String,
tags: Vec<String>,
}
/// Give the profile a new name and return the old one.
fn rename(p: &mut Profile, new_name: String) -> String {
mem::replace(&mut p.name, new_name)
}
/// Remove every tag and return them. Afterwards `p.tags` is an empty Vec.
fn take_tags(p: &mut Profile) -> Vec<String> {
mem::take(&mut p.tags)
}
fn main() {
let mut p = Profile {
name: String::from("Ada"),
tags: vec![String::from("admin"), String::from("beta")],
};
let ada_text = p.name.as_ptr();
let new_name = String::from("Grace");
let grace_text = new_name.as_ptr();
let old = rename(&mut p, new_name);
assert_eq!(old, "Ada");
assert_eq!(
old.as_ptr(),
ada_text,
"return the old String, do not copy it"
);
assert_eq!(p.name, "Grace");
assert_eq!(
p.name.as_ptr(),
grace_text,
"store the new String, do not copy it"
);
let admin_text = p.tags[0].as_ptr();
let tags = take_tags(&mut p);
assert_eq!(tags, ["admin", "beta"]);
assert_eq!(
tags[0].as_ptr(),
admin_text,
"hand over the tags, do not copy them"
);
assert!(p.tags.is_empty());
// Taking from an empty list is fine, and the profile still works.
assert!(take_tags(&mut p).is_empty());
p.tags.push(String::from("new"));
assert_eq!(p.tags, ["new"]);
println!("{}: all checks passed", p.name);
}
$ cargo run
Grace: all checks passed

Read more