RustMutable References

Mutable References in Rust

Immutable references let you read data without taking ownership. But what if you need to modify borrowed data? That's where mutable references come in. They give temporary write access to a value — but with strict rules that prevent data races at compile time.

Creating a Mutable Reference: &mut T

A mutable reference is created with &mut and its type is written as &mut T. To borrow a value mutably, the original variable must itself be declared mut.

RUST
fn main() {
    let mut s = String::from("hello"); // must be mut to borrow mutably

    change(&mut s);                    // pass a mutable reference

    println!("{}", s);                 // "hello, world"
}

fn change(s: &mut String) {           // accepts a mutable reference
    s.push_str(", world");            // modifying through the reference is fine
}
Note
Two things must align for mutable borrowing: the original variable must be `mut`, and you must explicitly write `&mut` when creating the reference. Rust doesn't silently make things mutable.
The Golden Rule of Mutable References

Rust enforces one fundamental rule about mutable references that prevents entire classes of bugs:

Warning
At any given time, you can have EITHER any number of immutable references (&T), OR exactly ONE mutable reference (&mut T) — but never both at the same time.

This rule exists to prevent data races — a class of bug where two pointers access the same memory simultaneously, at least one writes, and there's no synchronization. Data races cause undefined behaviour in C and C++ and are notoriously hard to debug. Rust eliminates them entirely at compile time.

Compiler Error: Two Mutable References

Trying to create two mutable references to the same value simultaneously is a compile error:

RUST
fn main() {
    let mut s = String::from("hello");

    let r1 = &mut s; // first mutable reference
    let r2 = &mut s; // ERROR: cannot borrow `s` as mutable more than once

    println!("{}, {}", r1, r2);
}
error[E0499]: cannot borrow `s` as mutable more than once at a time
 --> src/main.rs:5:14
  |
4 |     let r1 = &mut s;
  |              ------ first mutable borrow occurs here
5 |     let r2 = &mut s;
  |              ^^^^^^ second mutable borrow occurs here
6 |
7 |     println!("{}, {}", r1, r2);
  |                        -- first borrow later used here

The compiler tells you exactly what went wrong, where each borrow occurred, and where the first borrow is still in use. These error messages are designed to be read and acted on.

Compiler Error: Mixing &T and &mut T

You also cannot have an immutable reference and a mutable reference to the same value at the same time:

RUST
fn main() {
    let mut s = String::from("hello");

    let r1 = &s;      // immutable reference
    let r2 = &s;      // second immutable reference — this is fine
    let r3 = &mut s;  // ERROR: cannot borrow as mutable while immutable refs exist

    println!("{}, {}, {}", r1, r2, r3);
}
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
 --> src/main.rs:6:14
  |
4 |     let r1 = &s;
  |              -- immutable borrow occurs here
5 |     let r2 = &s;
6 |     let r3 = &mut s;
  |              ^^^^^^ mutable borrow occurs here
7 |
8 |     println!("{}, {}, {}", r1, r2, r3);
  |                            -- immutable borrow later used here

The reasoning: if a mutable reference existed alongside immutable ones, the immutable readers could observe data changing under them — violating the guarantee that their view is stable. Rust catches this statically.

Non-Lexical Lifetimes (NLL)

Since Rust 2018, the borrow checker uses Non-Lexical Lifetimes (NLL). Instead of treating a borrow as lasting until the end of the enclosing scope (the opening { to closing }), the compiler tracks when the last use of each reference occurs.

This means references end at their last use, not at the closing brace:

RUST
fn main() {
    let mut s = String::from("hello");

    let r1 = &s;
    let r2 = &s;
    println!("{} and {}", r1, r2);
    // r1 and r2 are no longer used after this line — their borrows end here

    let r3 = &mut s; // OK! immutable refs r1 and r2 are already done
    println!("{}", r3);
}
Success
NLL makes the borrow checker significantly more permissive and practical. Code that used to require awkward restructuring now compiles naturally.
The Variable Must Be Declared mut

You cannot take a mutable reference to an immutable variable. The mut keyword on the variable declaration is what grants mutable borrow permission:

RUST
fn main() {
    let s = String::from("hello");  // NOT declared mut

    // let r = &mut s; // ERROR: cannot borrow `s` as mutable, as it is not declared as mutable

    let mut t = String::from("hello"); // declared mut
    let r = &mut t;                    // OK
    r.push_str(", world");
    println!("{}", r);
}
error[E0596]: cannot borrow `s` as mutable, as it is not declared as mutable
 --> src/main.rs:4:13
  |
2 |     let s = String::from("hello");
  |         - help: consider changing this to be mutable: `mut s`
3 |
4 |     let r = &mut s;
  |             ^^^^^^ cannot borrow as mutable
Mutable References in Function Parameters

When a function needs to modify the data it receives, it takes a mutable reference parameter. This makes the intent explicit in the function signature — callers know the function will mutate their data.

RUST
fn append_exclamation(s: &mut String) {
    s.push('!');
}

fn double_vec(v: &mut Vec<i32>) {
    let len = v.len();
    for i in 0..len {
        let val = v[i];
        v.push(val);
    }
}

fn reset_to_zero(n: &mut i32) {
    *n = 0; // must deref explicitly for primitive types
}

fn main() {
    let mut greeting = String::from("Hello");
    append_exclamation(&mut greeting);
    println!("{}", greeting); // Hello!

    let mut numbers = vec![1, 2, 3];
    double_vec(&mut numbers);
    println!("{:?}", numbers); // [1, 2, 3, 1, 2, 3]

    let mut count = 42;
    reset_to_zero(&mut count);
    println!("{}", count); // 0
}
Tip
Notice that `reset_to_zero` uses `*n = 0` with an explicit dereference. For non-method operations on primitive types, you need to write the `*` to get through the reference to the value.
Practical Pattern: Modifying Vector Elements

A common use of mutable references is modifying individual elements in a collection in place:

RUST
fn main() {
    let mut scores = vec![70, 85, 92, 60, 78];

    // Boost all failing scores
    for score in &mut scores {
        if *score < 75 {
            *score += 10; // dereference to modify
        }
    }

    println!("{:?}", scores); // [80, 85, 92, 70, 88]
}

// Also works with a function
fn normalize(values: &mut Vec<f64>, factor: f64) {
    for v in values.iter_mut() {
        *v /= factor;
    }
}

fn main_normalize() {
    let mut data = vec![100.0, 200.0, 300.0];
    normalize(&mut data, 100.0);
    println!("{:?}", data); // [1.0, 2.0, 3.0]
}
Practical Pattern: Updating Struct Fields

RUST
struct Player {
    name: String,
    score: u32,
    level: u32,
}

fn award_points(player: &mut Player, points: u32) {
    player.score += points;
    if player.score >= player.level * 100 {
        player.level += 1;
        println!("{} leveled up to level {}!", player.name, player.level);
    }
}

fn main() {
    let mut p = Player {
        name: String::from("Alice"),
        score: 0,
        level: 1,
    };

    award_points(&mut p, 60);
    award_points(&mut p, 50); // triggers level up — score hits 110
    println!("Score: {}, Level: {}", p.score, p.level);
}
Alice leveled up to level 2!
Score: 110, Level: 2
Using Scopes to Enable Multiple Mutations

If you genuinely need multiple mutable references, you can use explicit scopes to ensure they don't overlap. The borrow ends when the reference goes out of scope:

RUST
fn main() {
    let mut s = String::from("hello");

    {
        let r1 = &mut s;
        r1.push_str(", world");
    } // r1 goes out of scope here — mutable borrow ends

    let r2 = &mut s; // OK now — r1 is gone
    r2.push('!');

    println!("{}", s); // hello, world!
}
Note
With Non-Lexical Lifetimes, you often don't need explicit scopes anymore — the compiler figures out when references are last used. But scopes are still a valid and sometimes clearest way to communicate intent.
Mutable References vs Mutable Variables

Concept

Syntax

What It Means

Mutable variable

let mut x = 5

x itself can be reassigned or modified

Mutable reference

let r = &mut x

r allows modifying the data x owns

Immutable ref to mut var

let r = &x

Can read x but not modify through r

Mut ref requires mut var

&mut x — x must be mut

Cannot mutably borrow an immutable binding

Interior Mutability: A Preview of RefCell

Sometimes you need multiple handles to the same data, with the ability to mutate it — but Rust's static rules prevent this. For these cases, Rust offers interior mutability patterns, where mutation is checked at runtime instead of compile time.

The most common tool for this is RefCell&lt;T&gt;:

RUST
use std::cell::RefCell;

fn main() {
    // RefCell allows mutation through shared (&) references
    let data = RefCell::new(vec![1, 2, 3]);

    // Borrow immutably
    println!("{:?}", data.borrow()); // [1, 2, 3]

    // Borrow mutably — checked at runtime, not compile time
    data.borrow_mut().push(4);

    println!("{:?}", data.borrow()); // [1, 2, 3, 4]
}
Warning
RefCell moves the borrow checking to runtime. If you violate the borrowing rules (e.g. two mutable borrows at once), it panics at runtime instead of failing at compile time. Use it sparingly and only when the static rules are genuinely too restrictive.
Why the Rules Make Rust Safer

The mutable reference rules eliminate two major categories of bugs:

Bug Type

What Happens in C/C++

What Rust Does

Data race

Two threads write to same memory — undefined behaviour

Compile error: only one &mut T at a time

Iterator invalidation

Modifying a vector while iterating it crashes

Compile error: cannot have &mut and & simultaneously

Unexpected mutation

Function mutates data caller didn't expect to change

Caller must explicitly pass &mut — mutation is visible at call site

Aliased mutation

Two pointers modify the same memory unpredictably

Impossible: &mut T is always exclusive

Summary
  • Mutable references (&mut T) allow modifying borrowed data

  • The original variable must be declared mut to take a mutable reference

  • At any given time: any number of &T OR exactly one &mut T — never both

  • This rule prevents data races at compile time, with zero runtime cost

  • Two simultaneous &mut references to the same value are a compile error

  • An &T and &mut T to the same value cannot coexist

  • Non-Lexical Lifetimes: borrows end at last use, not end of scope

  • Use explicit scopes to sequence mutations when NLL isn't enough

  • RefCell provides runtime-checked interior mutability for special cases