Variables and Mutability

Mutable Variables with mut

The mut keyword makes a variable binding mutable: the variable can be assigned a new value, and the value it holds can be modified in place. mut is written between let and the name.

fn main() {
    let mut x = 5;
    println!("x = {x}");
    x = 6;
    println!("x = {x}");
    x += 10;
    x *= 2;
    println!("x = {x}");
}
x = 5
x = 6
x = 32

Syntax

let mut name: Type = value;
    ---
     |
     +-- allows reassignment and mutable borrows of this binding

mut is part of the binding, not part of the type. The type of x above is i32, not "mutable i32". The same type is used whether the binding is mutable or not.

What mut Allows

A mutable binding permits two kinds of operations that an immutable binding rejects:

Operation Example Needs mut
Assign a new value x = 6; yes
Compound assignment x += 1; yes
Assign to a field, element, or tuple position p.x = 3;, a[0] = 1;, t.0 = 2; yes
Call a method that takes &mut self v.push(1); yes
Take a mutable reference &mut x yes
Read the value println!("{x}") no

The next example uses mut with several kinds of values:

fn main() {
    let mut numbers = vec![1, 2, 3];
    numbers.push(4);
    numbers[0] = 100;
    numbers.retain(|n| n % 2 == 0);
    println!("{numbers:?}");

    let mut text = String::from("Hello");
    text.push_str(", world");
    text.push('!');
    println!("{text}");

    let mut scores = [10, 20, 30];
    scores[1] += 5;
    println!("{scores:?}");

    let mut pair = (1, "one");
    pair.0 = 2;
    pair.1 = "two";
    println!("{pair:?}");
}
[100, 2, 4]
Hello, world!
[10, 25, 30]
(2, "two")

Structs

Mutability applies to the whole struct binding. With mut, every field can be assigned, and fields can be modified through methods:

struct Counter {
    value: u32,
    label: String,
}

fn main() {
    let mut counter = Counter {
        value: 0,
        label: String::from("clicks"),
    };
    counter.value += 1;
    counter.label.push_str("!");
    println!("{} {}", counter.label, counter.value);
}
clicks! 1

The Type Cannot Change

A mutable variable can hold a different value, but never a value of a different type. The type is fixed when the variable is declared.

// ❌ does not compile
fn main() {
    let mut value = 5;
    value = "five";
    println!("{value}");
}
error[E0308]: mismatched types
 --> main.rs:4:13
  |
3 |     let mut value = 5;
  |                     - expected due to this value
4 |     value = "five";
  |             ^^^^^^ expected integer, found `&str`

To reuse a name with a new type, use shadowing (let value = "five";), which creates a new variable.

The Unused mut Warning

The compiler warns when a variable is declared mut but is never mutated:

fn main() {
    let mut count = 0;
    println!("{count}");
}
warning: variable does not need to be mutable
 --> main.rs:2:9
  |
2 |     let mut count = 0;
  |         ----^^^^^
  |         |
  |         help: remove this `mut`
  |
  = note: `#[warn(unused_mut)]` (part of `#[warn(unused)]`) on by default

The warning exists because every mut is a claim to the reader that the variable changes. A mut that does not change anything is misleading.

Copying Versus Aliasing

Assigning to a mutable variable changes only that variable. A copy made earlier is not affected:

fn main() {
    let mut x = 5;
    let y = x;
    x = 6;
    println!("{x} {y}");

    let mut s1 = String::from("a");
    let s2 = s1.clone();
    s1.push('b');
    println!("{s1} {s2}");
}
6 5
ab a

y is a copy of the integer, and s2 is an independent clone of the string. Neither one observes the later change.

Mutable Parameters and Mutable References

A function parameter is an ordinary binding, so it needs mut to be modified. A mut parameter changes only the function's own copy:

fn add_one(mut n: i32) -> i32 {
    n += 1;
    n
}

fn add_one_in_place(n: &mut i32) {
    *n += 1;
}

fn main() {
    let a = 5;
    let b = add_one(a);
    println!("{a} {b}");

    let mut c = 5;
    add_one_in_place(&mut c);
    println!("{c}");
}
5 6
6
Declaration Meaning
fn f(mut n: i32) The function owns a copy of n and may change that copy. The caller sees no change
fn f(n: &mut i32) The function borrows the caller's variable mutably and can change it. The caller sees the change

The caller must itself have a mut variable to lend: &mut c requires that c be declared let mut c.

mut x Versus &mut

mut on a binding and &mut in a type are different things. let mut r = &x makes the variable r reassignable, while the value it points to is still read-only. let r = &mut x makes r a fixed reference through which the pointed-to value can be changed.

fn main() {
    let mut total = 10;
    let r = &mut total;
    *r += 5;
    println!("{total}");

    let first = 1;
    let second = 2;
    let mut pointer = &first;
    println!("{pointer}");
    pointer = &second;
    println!("{pointer}");
}
15
1
2
Declaration Variable can be reassigned Pointed-to value can be changed
let r = &x; no no
let mut r = &x; yes no
let r = &mut x; no yes
let mut r = &mut x; yes yes

Mutable Borrowing Rules

Mutation through a reference is controlled by two rules that the compiler checks:

  1. At any time there can be either any number of immutable references (&T) or exactly one mutable reference (&mut T) to a value.
  2. References must always point to valid data.

Two mutable borrows alive at the same time are rejected:

// ❌ does not compile
fn main() {
    let mut value = 1;
    let a = &mut value;
    let b = &mut value;
    *a += 1;
    *b += 1;
    println!("{value}");
}
error[E0499]: cannot borrow `value` as mutable more than once at a time
 --> main.rs:5:13
  |
4 |     let a = &mut value;
  |             ---------- first mutable borrow occurs here
5 |     let b = &mut value;
  |             ^^^^^^^^^^ second mutable borrow occurs here
6 |     *a += 1;
  |     ------- first borrow later used here

Modifying a collection while looping over it is the same kind of conflict: the loop holds an immutable borrow, so the body cannot take a mutable one.

// ❌ does not compile
fn main() {
    let mut numbers = vec![1, 2, 3];
    for n in &numbers {
        if *n == 2 {
            numbers.push(4);
        }
    }
}
error[E0502]: cannot borrow `numbers` as mutable because it is also borrowed as immutable
 --> main.rs:6:13
  |
4 |       for n in &numbers {
  |       -        --------
  |       |        |
  |       |        immutable borrow occurs here
  |  _____|        immutable borrow later used here
  | |
5 | |         if *n == 2 {
6 | |             numbers.push(4);
  | |             ^^^^^^^^^^^^^^^ mutable borrow occurs here
7 | |         }
8 | |     }
  | |_____- this for loop borrows `numbers` immutably, preventing mutation within its body
  |
  = help: consider using an index-based loop instead, or collecting modifications into a separate collection

If push were allowed, the vector could reallocate while the loop still holds a pointer into the old buffer. Rust rejects the program instead.

Common Uses of mut

Accumulators and counters in loops:

fn main() {
    let mut sum = 0;
    for i in 1..=5 {
        sum += i;
    }
    println!("{sum}");

    let mut n = 27;
    let mut steps = 0;
    while n != 1 {
        n = if n % 2 == 0 { n / 2 } else { 3 * n + 1 };
        steps += 1;
    }
    println!("{steps}");
}
15
111

Swapping values through mutable references:

fn main() {
    let mut a = 1;
    let mut b = 2;
    std::mem::swap(&mut a, &mut b);
    println!("{a} {b}");
}
2 1

Closures that change captured state need the closure variable itself to be mut, because calling it mutates what it captured:

fn main() {
    let mut counter = 0;
    let mut increment = || {
        counter += 1;
        counter
    };
    increment();
    increment();
    println!("{counter}");
}
2

mut in Patterns

mut can be applied to individual names inside a pattern. Only the marked names are mutable:

fn main() {
    let (mut a, b) = (1, 2);
    a += b;
    println!("{a} {b}");
}
3 2

b stays immutable. The same syntax works in function parameters and in for loops (for mut item in items).

Keeping Mutation Local

A mutable variable is only needed while the value is being built or changed. After that, the value can be rebound to an immutable name by shadowing, which prevents later accidental changes:

fn main() {
    let mut values = vec![3, 1, 2];
    values.sort();
    let values = values;
    println!("{values:?}");
}
[1, 2, 3]

let values = values; moves the vector into a new immutable binding with the same name. From that line on, the vector can only be read.

A block can confine the mutable variable entirely:

fn main() {
    let values = {
        let mut temp = vec![3, 1, 2];
        temp.sort();
        temp
    };
    println!("{values:?}");
}
[1, 2, 3]

Here values is immutable from the start, and temp exists only inside the block.