Guide contents

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 &mut reference 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.

Rust
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}");
}
Output
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

Rust✗ does not compile · E0596
fn add_exclamation(s: &mut String) {
    s.push_str("!");
}

fn main() {
    let greeting = String::from("Hello");
    add_exclamation(&mut greeting);
    println!("{greeting}");
}
Output
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.

Rust✗ does not compile · E0308
fn add_exclamation(s: &mut String) {
    s.push_str("!");
}

fn main() {
    let mut greeting = String::from("Hello");
    add_exclamation(&greeting);
    println!("{greeting}");
}
Output
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 *.

Rust
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}");
}
Output
page views: 3

5. Changing a Vec through &mut

Rust
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);
}
Output
["bug", "new", "unreviewed"]

6. Two mutable references at once are not allowed

Rust✗ does not compile · E0499
fn main() {
    let mut balance = 100;
    let a = &mut balance;
    let b = &mut balance;
    *a += 10;
    *b -= 20;
    println!("{balance}");
}
Output
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 here

If 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.

Rust✗ does not compile · E0502
fn main() {
    let mut scores = vec![10, 20, 30];
    let first = &scores[0];
    scores.push(40);
    println!("first score: {first}");
}
Output
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 here

The 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.

Text
 before push:   first ──► [10][20][30]          (capacity 3, full)

 after push:    first ──► [??][??][??]          old block, freed!
                          [10][20][30][40][ ][ ] new block

In 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.

Rust
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);
}
Output
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.

Rust
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);
}
Output
first score was 10; now [10, 20, 30, 40]

11. Modifying a Vec while looping over it

The compiler catches this bug:

Rust✗ does not compile · E0502
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);
}
Output
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 here

12. The fix: collect the changes, then apply them

Rust
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);
}
Output
["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.

Rust
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);
}
Output
[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.

Rust
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);
}
Output
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.

Rust✗ does not compile · E0382
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}");
}
Output
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 move

16. 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.

Rust
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);
}
Output
["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.

Rust
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);
}
Output
["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.

Rust✗ does not compile · E0499
fn main() {
    let mut accounts = vec![100, 50];
    let from = &mut accounts[0];
    let to = &mut accounts[1];
    *from -= 30;
    *to += 30;
    println!("{:?}", accounts);
}
Output
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-slices

The simplest fix is to change the elements one after the other through their indexes. For swapping, Vec has swap.

Rust
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);
}
Output
[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.

Rust
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);
}
Output
(3, 1)

20. std::mem::swap exchanges two values

swap takes two &mut references and exchanges the values behind them, without cloning.

Rust
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}");
}
Output
active: green deployment, standby: blue deployment

21. 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.

Rust
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:?}");
}
Output
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.

Rust
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);
}
Output
sent: ["event 1", "event 2"]
buffer now: []

23. Moving out of a &mut directly is an error

This is why take and replace exist:

Rust✗ does not compile · E0507
fn flush(buffer: &mut Vec<String>) -> Vec<String> {
    *buffer
}

fn main() {
    let mut buffer = vec![String::from("event")];
    println!("{:?}", flush(&mut buffer));
}
Output
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.

Rust
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}");
    }
}
Output
true
false
false
alice: 300
bob: 320

25. 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.

Rust
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}");
    }
}
Output
stock: [("apple", 0), ("pear", 2)]
problem: not enough pear: wanted 5, have 2
problem: unknown item kiwi

stock.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

Rust
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);
}
Output
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.)

Rust
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);
}
Output
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.

Rust
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}");
    }
}
Output
frame 0: #.......
frame 1: .#......
frame 2: ..#.....

front.iter().collect() turns the characters into a String; Iterators explains collect.

Summary

  • &mut T is a mutable reference. The variable must be mut, and the call site must write &mut value.
  • One &mut or 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.
  • &mut references are not Copy, but Rust reborrows them automatically when you pass them on.
  • Use std::mem::swap, replace, and take to move values in and out of a &mut.

Next: Slices.