Lesson 15 of 94
Mutable References
A shared reference &T lets you read a value you don't own. A mutable reference &mut T lets you change it. With that power comes Rust's most important rule:
At any given time, you can have either one mutable reference or any number of shared references to a value, but not both.
This rule is what makes Rust free of data races, and it also catches a family of single-threaded bugs at compile time, such as changing a collection while looping over it.
- Only a
&mutreference can modify a borrowed value. - One writer or many readers, checked at compile time.
- Modifying a list while iterating over it does not compile.
- Two threads writing the same value without a lock is impossible: see Concurrency.
1. Changing a value through &mut
Three things are needed: the variable must be mut, the call site passes &mut value, and the parameter type is &mut T.
fn add_exclamation(s: &mut String) {
s.push_str("!");
}
fn main() {
let mut greeting = String::from("Hello");
add_exclamation(&mut greeting);
add_exclamation(&mut greeting);
println!("{greeting}");
}Hello!!The caller still owns greeting. The function just borrowed it with write access for the duration of the call.
2. Only a mut variable can be borrowed mutably
fn add_exclamation(s: &mut String) {
s.push_str("!");
}
fn main() {
let greeting = String::from("Hello");
add_exclamation(&mut greeting);
println!("{greeting}");
}error[E0596]: cannot borrow `greeting` as mutable, as it is not declared as mutable
--> src/main.rs:7:21
|
7 | add_exclamation(&mut greeting);
| ^^^^^^^^^^^^^ cannot borrow as mutable
|
help: consider changing this to be mutable
|
6 | let mut greeting = String::from("Hello");
| +++3. The call site must say &mut
Rust makes mutation visible at the call site. Passing &greeting where &mut String is expected is an error, so a reader of main can always see which calls might change a variable.
fn add_exclamation(s: &mut String) {
s.push_str("!");
}
fn main() {
let mut greeting = String::from("Hello");
add_exclamation(&greeting);
println!("{greeting}");
}error[E0308]: mismatched types
--> src/main.rs:7:21
|
7 | add_exclamation(&greeting);
| --------------- ^^^^^^^^^ types differ in mutability
| |
| arguments to this function are incorrect
|
= note: expected mutable reference `&mut String`
found reference `&String`
note: function defined here
--> src/main.rs:1:4
|
1 | fn add_exclamation(s: &mut String) {
| ^^^^^^^^^^^^^^^ --------------4. Changing a number through &mut
To change a value of a simple type through a reference, dereference it with *.
fn increment(counter: &mut u32) {
*counter += 1;
}
fn main() {
let mut page_views = 0;
increment(&mut page_views);
increment(&mut page_views);
increment(&mut page_views);
println!("page views: {page_views}");
}page views: 35. Changing a Vec through &mut
fn add_default_tags(tags: &mut Vec<String>) {
tags.push(String::from("new"));
tags.push(String::from("unreviewed"));
}
fn main() {
let mut tags = vec![String::from("bug")];
add_default_tags(&mut tags);
println!("{:?}", tags);
}["bug", "new", "unreviewed"]6. Two mutable references at once are not allowed
fn main() {
let mut balance = 100;
let a = &mut balance;
let b = &mut balance;
*a += 10;
*b -= 20;
println!("{balance}");
}error[E0499]: cannot borrow `balance` as mutable more than once at a time
--> src/main.rs:4:13
|
3 | let a = &mut balance;
| ------------ first mutable borrow occurs here
4 | let b = &mut balance;
| ^^^^^^^^^^^^ second mutable borrow occurs here
5 | *a += 10;
| -------- first borrow later used hereIf two references could both change balance at the same time, neither could rely on what the value is. With threads, this is exactly a data race. Rust rules it out even in single-threaded code, which also rules out many subtle bugs.
7. A mutable reference while a shared one exists is not allowed
Readers expect the value not to change under them. So while any & reference is in use, no &mut reference can be created.
fn main() {
let mut scores = vec![10, 20, 30];
let first = &scores[0];
scores.push(40);
println!("first score: {first}");
}error[E0502]: cannot borrow `scores` as mutable because it is also borrowed as immutable
--> src/main.rs:4:5
|
3 | let first = &scores[0];
| ------ immutable borrow occurs here
4 | scores.push(40);
| ^^^^^^^^^^^^^^^ mutable borrow occurs here
5 | println!("first score: {first}");
| ----- immutable borrow later used hereThe next examples explain why this particular case would be a real bug.
8. Why: pushing can move the vector's elements
When a Vec is full, push allocates a bigger block of memory, moves the elements there, and frees the old block. A reference to an element would then point into freed memory.
before push: first ──► [10][20][30] (capacity 3, full)
after push: first ──► [??][??][??] old block, freed!
[10][20][30][40][ ][ ] new blockIn C++, it is a classic crash. In Rust, it does not compile.
9. Borrows end at their last use
A borrow lasts only until its last use, so you can use shared and mutable references one after another. This compiles, because first is no longer used when push runs.
fn main() {
let mut scores = vec![10, 20, 30];
let first = &scores[0];
println!("first score: {first}"); // last use of `first`
scores.push(40); // fine now
println!("{:?}", scores);
}first score: 10
[10, 20, 30, 40]10. Copy the value out instead of keeping a reference
When you need the value after changing the vector, copy (or clone) it first, so you no longer hold a borrow.
fn main() {
let mut scores = vec![10, 20, 30];
let first = scores[0]; // a copy, not a reference
scores.push(40);
println!("first score was {first}; now {:?}", scores);
}first score was 10; now [10, 20, 30, 40]11. Modifying a Vec while looping over it
The compiler catches this bug:
fn main() {
let mut tasks = vec![String::from("write"), String::from("test")];
for task in &tasks {
if task == "test" {
tasks.push(String::from("fix bugs found in testing"));
}
}
println!("{:?}", tasks);
}error[E0502]: cannot borrow `tasks` as mutable because it is also borrowed as immutable
--> src/main.rs:5:13
|
3 | for task in &tasks {
| ------
| |
| immutable borrow occurs here
| immutable borrow later used here
4 | if task == "test" {
5 | tasks.push(String::from("fix bugs found in testing"));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here12. The fix: collect the changes, then apply them
fn main() {
let mut tasks = vec![String::from("write"), String::from("test")];
let mut to_add = Vec::new();
for task in &tasks {
if task == "test" {
to_add.push(String::from("fix bugs found in testing"));
}
}
for t in to_add {
tasks.push(t);
}
println!("{:?}", tasks);
}["write", "test", "fix bugs found in testing"]13. Changing every element with for x in &mut v
Each x is a &mut T to one element, so you can change the elements (but not the vector's length) inside the loop.
fn main() {
let mut prices: Vec<f64> = vec![19.99, 5.49, 102.0];
for price in &mut prices {
*price = (*price * 1.2 * 100.0).round() / 100.0; // add 20% VAT
}
println!("{:?}", prices);
let mut names = vec![String::from("ana"), String::from("ben")];
for name in &mut names {
name.push_str("@example.com"); // methods auto-dereference
}
println!("{:?}", names);
}[23.99, 6.59, 122.4]
["ana@example.com", "ben@example.com"]14. Method calls borrow mutably for you
When you call a method that needs &mut self, such as push, Rust takes the &mut automatically. That is why v.push(1) works without writing (&mut v).push(1). The variable still has to be mut.
fn main() {
let mut log = Vec::new();
log.push("started"); // same as Vec::push(&mut log, "started")
log.push("running");
let n = log.len(); // same as Vec::len(&log)
println!("{n} entries: {:?}", log);
}2 entries: ["started", "running"]15. Mutable references are not Copy
Copying a &mut would create two mutable references to the same value, which the rule forbids. So assigning a &mut moves it.
fn main() {
let mut total = 0;
let r1 = &mut total;
let r2 = r1; // r1 is moved into r2
*r2 += 5;
*r1 += 1; // error: r1 was moved
println!("{total}");
}error[E0382]: use of moved value: `r1`
--> src/main.rs:6:5
|
3 | let r1 = &mut total;
| -- move occurs because `r1` has type `&mut i32`, which does not implement the `Copy` trait
4 | let r2 = r1; // r1 is moved into r2
| -- value moved here
5 | *r2 += 5;
6 | *r1 += 1; // error: r1 was moved
| ^^^^^^^^ value used here after move16. Reborrowing: passing &mut to several calls
If &mut moves, how can you pass the same &mut to a function twice? Rust automatically reborrows: it creates a short, temporary &mut *r for each call, and your original reference becomes usable again once that call returns.
fn add_item(cart: &mut Vec<String>, item: &str) {
cart.push(item.to_string());
}
fn fill_cart(cart: &mut Vec<String>) {
add_item(cart, "milk"); // reborrows `cart` for this call only
add_item(cart, "bread"); // ...and again
cart.push(String::from("eggs"));
}
fn main() {
let mut cart = Vec::new();
fill_cart(&mut cart);
println!("{:?}", cart);
}["milk", "bread", "eggs"]17. Returning a &mut into the argument
A function can hand back a mutable reference to part of the data it was given.
fn last_line(lines: &mut Vec<String>) -> &mut String {
let index = lines.len() - 1;
&mut lines[index]
}
fn main() {
let mut document = vec![String::from("Title"), String::from("Body text")];
let line = last_line(&mut document);
line.push_str(" (edited)");
println!("{:?}", document);
}["Title", "Body text (edited)"]18. Two elements of a Vec at once
Borrowing two elements mutably through indexing is rejected, because the compiler cannot prove the two indexes differ.
fn main() {
let mut accounts = vec![100, 50];
let from = &mut accounts[0];
let to = &mut accounts[1];
*from -= 30;
*to += 30;
println!("{:?}", accounts);
}error[E0499]: cannot borrow `accounts` as mutable more than once at a time
--> src/main.rs:4:19
|
3 | let from = &mut accounts[0];
| -------- first mutable borrow occurs here
4 | let to = &mut accounts[1];
| ^^^^^^^^ second mutable borrow occurs here
5 | *from -= 30;
| ----------- first borrow later used here
|
= help: use `.split_at_mut(position)` to obtain two mutable non-overlapping sub-slicesThe simplest fix is to change the elements one after the other through their indexes. For swapping, Vec has swap.
fn main() {
let mut accounts = vec![100, 50];
accounts[0] -= 30;
accounts[1] += 30;
println!("{:?}", accounts);
let mut queue = vec!["low", "high", "medium"];
queue.swap(0, 1);
println!("{:?}", queue);
}[70, 80]
["high", "low", "medium"]split_at_mut gives two non-overlapping mutable views of a vector, for the rare cases that really need two &mut at once; see Slices.
19. Different tuple fields can be borrowed separately
The compiler can see that two fields of a tuple do not overlap, so borrowing both mutably is fine.
fn main() {
let mut stats = (0, 0); // (successes, failures)
let successes = &mut stats.0;
let failures = &mut stats.1;
*successes += 3;
*failures += 1;
println!("{:?}", stats);
}(3, 1)20. std::mem::swap exchanges two values
swap takes two &mut references and exchanges the values behind them, without cloning.
fn main() {
let mut active = String::from("blue deployment");
let mut standby = String::from("green deployment");
std::mem::swap(&mut active, &mut standby);
println!("active: {active}, standby: {standby}");
}active: green deployment, standby: blue deployment21. std::mem::replace swaps in a new value and returns the old one
Through a &mut reference you cannot move the value out, because that would leave nothing behind. replace solves this: it puts a new value in and gives you the old one.
fn main() {
let mut current_log = String::from("line 1\nline 2");
let archived = std::mem::replace(&mut current_log, String::from("new log started"));
println!("archived: {archived:?}");
println!("current: {current_log:?}");
}archived: "line 1\nline 2"
current: "new log started"22. std::mem::take takes the value and leaves an empty one
take is replace with the type's default "empty" value: an empty String, an empty Vec, zero. It is the standard way to move a value out of a &mut.
fn flush(buffer: &mut Vec<String>) -> Vec<String> {
std::mem::take(buffer)
}
fn main() {
let mut buffer = vec![String::from("event 1"), String::from("event 2")];
let batch = flush(&mut buffer);
println!("sent: {:?}", batch);
println!("buffer now: {:?}", buffer);
}sent: ["event 1", "event 2"]
buffer now: []23. Moving out of a &mut directly is an error
This is why take and replace exist:
fn flush(buffer: &mut Vec<String>) -> Vec<String> {
*buffer
}
fn main() {
let mut buffer = vec![String::from("event")];
println!("{:?}", flush(&mut buffer));
}error[E0507]: cannot move out of `*buffer` which is behind a mutable reference
--> src/main.rs:2:5
|
2 | *buffer
| ^^^^^^^ move occurs because `*buffer` has type `Vec<String>`, which does not implement the `Copy` trait
|
help: consider cloning the value if the performance cost is acceptable
|
2 - *buffer
2 + buffer.clone()
|24. Realistic example: transferring money between accounts
The function borrows the account list mutably and validates the transfer before changing anything.
fn transfer(accounts: &mut Vec<(String, i64)>, from: usize, to: usize, amount: i64) -> bool {
if from == to || amount <= 0 || accounts[from].1 < amount {
return false;
}
accounts[from].1 -= amount;
accounts[to].1 += amount;
true
}
fn main() {
let mut accounts = vec![
(String::from("alice"), 500),
(String::from("bob"), 120),
];
println!("{}", transfer(&mut accounts, 0, 1, 200));
println!("{}", transfer(&mut accounts, 1, 0, 1_000));
println!("{}", transfer(&mut accounts, 0, 0, 10));
for (name, balance) in &accounts {
println!("{name}: {balance}");
}
}true
false
false
alice: 300
bob: 32025. Realistic example: applying orders to inventory
One parameter is read (&) and the other is changed (&mut). The signature tells you exactly what the function will and won't modify.
fn apply_orders(stock: &mut Vec<(String, u32)>, orders: &Vec<(String, u32)>) -> Vec<String> {
let mut problems = Vec::new();
for (item, wanted) in orders {
let mut found = false;
for (name, available) in stock.iter_mut() {
if name == item {
found = true;
if *available >= *wanted {
*available -= wanted;
} else {
problems.push(format!("not enough {item}: wanted {wanted}, have {available}"));
}
}
}
if !found {
problems.push(format!("unknown item {item}"));
}
}
problems
}
fn main() {
let mut stock = vec![(String::from("apple"), 10), (String::from("pear"), 2)];
let orders = vec![
(String::from("apple"), 4),
(String::from("pear"), 5),
(String::from("kiwi"), 1),
(String::from("apple"), 6),
];
let problems = apply_orders(&mut stock, &orders);
println!("stock: {:?}", stock);
for p in &problems {
println!("problem: {p}");
}
}stock: [("apple", 0), ("pear", 2)]
problem: not enough pear: wanted 5, have 2
problem: unknown item kiwistock.iter_mut() is the method form of &mut stock in a for loop. It is needed here because stock is already a reference; Iterators covers iter and iter_mut.
26. Realistic example: normalizing emails in place
fn normalize_emails(emails: &mut Vec<String>) -> usize {
let mut changed = 0;
for email in emails.iter_mut() {
let normalized = email.trim().to_lowercase();
if *email != normalized {
*email = normalized;
changed += 1;
}
}
changed
}
fn main() {
let mut emails = vec![
String::from(" Ana@Example.com"),
String::from("ben@example.com"),
String::from("CY@EXAMPLE.COM "),
];
let changed = normalize_emails(&mut emails);
println!("changed {changed}: {:?}", emails);
}changed 2: ["ana@example.com", "ben@example.com", "cy@example.com"]27. Realistic example: removing items while scanning
Removing elements while walking a vector by index is a classic source of skipped elements. Here the index only advances when nothing was removed. (Rust also has retain, covered with closures later, which does this in one line.)
fn remove_expired(sessions: &mut Vec<(String, u32)>, now: u32) -> Vec<String> {
let mut removed = Vec::new();
let mut i = 0;
while i < sessions.len() {
if sessions[i].1 <= now {
let (id, _) = sessions.remove(i);
removed.push(id);
} else {
i += 1;
}
}
removed
}
fn main() {
let mut sessions = vec![
(String::from("s1"), 100),
(String::from("s2"), 250),
(String::from("s3"), 90),
(String::from("s4"), 300),
];
let expired = remove_expired(&mut sessions, 200);
println!("expired: {:?}", expired);
println!("active: {:?}", sessions);
}expired: ["s1", "s3"]
active: [("s2", 250), ("s4", 300)]28. Realistic example: a double buffer
A renderer draws into a back buffer, then swaps it with the front buffer that is on screen. mem::swap exchanges the two vectors without copying any pixels, and mem::take clears the back buffer for the next frame.
fn draw_frame(back: &mut Vec<char>, frame: usize) {
for i in 0..8 {
back.push(if i == frame % 8 { '#' } else { '.' });
}
}
fn main() {
let mut front: Vec<char> = Vec::new();
let mut back: Vec<char> = Vec::new();
for frame in 0..3 {
draw_frame(&mut back, frame);
std::mem::swap(&mut front, &mut back);
let _old = std::mem::take(&mut back);
let shown: String = front.iter().collect();
println!("frame {frame}: {shown}");
}
}frame 0: #.......
frame 1: .#......
frame 2: ..#.....front.iter().collect() turns the characters into a String; Iterators explains collect.
Summary
&mut Tis a mutable reference. The variable must bemut, and the call site must write&mut value.- One
&mutor any number of&, never both at the same time. This rules out data races and iterator invalidation. - A borrow lasts until its last use, so borrows that don't overlap never conflict.
- Changing a vector while looping over it does not compile. Collect the changes first, or loop by index.
&mutreferences are notCopy, but Rust reborrows them automatically when you pass them on.- Use
std::mem::swap,replace, andtaketo move values in and out of a&mut.
Next: Slices.
