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
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:?}");}[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)}[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)}[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)[15, 25, 0]
// arrays are passed by reference, so the function can change the caller's arrayfunction 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);[ 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); }}[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);}[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 listvoid 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;}[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";}[15, 25, 0]
What's different
&mut xlendsxout to be changed. A plain&only looks. Because&mutis written at the call, you can see which calls change your data.*rmeans "the valuerpoints at". Method calls and printing look through a reference for you, so you mostly see*when assigning:*r += 1.- You need
mutin 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
SendandSyncrules for threads (Send and Sync). It is the rule of a read-write lock (many readers or one writer), but checked while compiling.
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.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)}[11 20 31]
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.
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.
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.
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)}]");[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;}[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";}[11, 20, 31]
What does the compiler say? Guess first, then open.
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
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.)
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.
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.
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.
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.
C# allows two ref locals into the same array and prints [11, 20, 31]. Nothing checks whether two refs point at the same slot.
C allows any number of pointers that write into the same array. This prints [11, 20, 31].
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.
&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[_].)
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.
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:?}");}[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:
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}");}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:
fn add_one(x: &mut i32) { *x += 1;}
fn main() { let n = 5; add_one(&mut n); println!("n: {n}");}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:
fn add_item(items: &Vec<i32>) { items.push(0);}
fn main() { let mut items = vec![1, 2, 3]; add_item(&items); println!("items: {items:?}");}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
&mutborrow (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:
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}");}a: 10, b: 10, c: 10, n: 10
Two writers is E0499:
fn main() { let mut n = 10; let a = &mut n; let b = &mut n; *a += 1; *b += 1; println!("n: {n}");}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:
fn main() { let mut n = 10; let reader = &n; let writer = &mut n; *writer += 1; println!("reader sees {reader}");}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:
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();}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:
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:?}");}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:
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}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:
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:?}");}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:
fn main() { let mut v = vec![1, 2, 3]; for x in &v { if *x == 2 { v.push(20); } } println!("v: {v:?}");}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:
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:?}");}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:
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}");}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:
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}");}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
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:?}");}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:
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();}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:
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}");}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)putsnewinxand returns the old value.mem::take(&mut x)isreplacewith the type’s default value ("", an emptyVec,0).
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:?}");}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:
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:?}");}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:
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);}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:
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);}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:
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:?}");}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:
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:?}");}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
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}");}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:
v and first lead to the same buffer. So far this is fine, and the compiler accepts it.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:
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);}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
SendandSync(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 TandTusually say whether a function reads, changes or takes its argument. (Interior-mutable types change through a&; aCopytype isn’t consumed.) - Optimisation the compiler can trust. If a
&mutis the only way to reach a value, the compiler can assume nothing else changes it. Withrustc -O --emit=llvm-ira&mut i32parameter comes out markednoaliasfor 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).
/// 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
/// 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");}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.
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
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");}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.
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
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);}Grace: all checks passed
Read more
- Reading compiler errors: how an error is laid out, and every error code on this site with what it means and where to see it.
- The Rust Book, chapter 4.2: References and Borrowing. Mutable references and the “one writer or many readers” rule, in the Book’s words.
- Rust by Example: Mutability (borrowing) for
&mut, and Mutability (variable bindings) forlet mut. std::mem::swap,replaceandtake.slice::split_at_mutandslice::swap(Vecgets both through its slice).- The Rustonomicon: Aliasing, for why the compiler cares and what it assumes.
- The rustc dev guide: Two-phase borrows, for why
v.push(v.len())works.
Learn Rust is an independent project, not affiliated with the Rust Project or the Rust Foundation. Code and text are under the MIT licence.Source on GitHub.