Error E0596

"cannot borrow as mutable, as it is not declared as mutable" — How to Fix

This error occurs because you are attempting to mutate a variable that was declared as immutable, which Rust enforces by default.

The variable you can't change

You're building a high-score tracker. You read the current score, calculate the bonus, and try to update the total. The compiler stops you with E0596. You stare at the code. You declared the variable. You're assigning to it. What's the problem?

Rust thinks you're trying to mutate something you promised wouldn't change. The fix is almost always adding mut, but knowing exactly where to add it requires understanding how Rust separates the variable binding from the data it holds.

Bindings are immutable by default

Rust treats every variable as immutable unless you say otherwise. This isn't a restriction; it's a design choice that makes code easier to reason about. When you see let x = 5;, you can trust x never changes in that scope. If a value evolves, the mut keyword signals that intent to anyone reading the code.

Think of a variable binding like a label on a box. By default, the label is glued down. You can't swap the box for a different one. You also can't open the box and rearrange the contents unless the box itself allows it. The mut keyword is the glue remover. It lets you swap the box. If the box is sealed, mut on the label doesn't help you open the box.

E0596 fires when you try to mutate a binding that lacks mut. The compiler is enforcing your declaration. It doesn't care if the mutation is "safe" or if the value is only used locally. It cares about the contract you wrote.

Minimal example

The simplest case is a local variable. You declare it, then try to reassign it.

fn main() {
    // x is immutable by default. Rust assumes you won't change it.
    let x = 5;

    // E0596: cannot assign to immutable variable `x`.
    // The compiler rejects this because `x` lacks the `mut` keyword.
    x = 6;
}

The fix is adding mut to the binding. The permission lives where the variable is born.

fn main() {
    // `mut` marks the binding as mutable. You can reassign `x` now.
    let mut x = 5;
    x = 6;
    println!("{}", x);
}

Add mut to the binding, not the assignment. The compiler checks the declaration, not the usage.

How the compiler checks mutability

When the compiler processes let x = 5;, it records x as an immutable binding in the symbol table. Later, when it encounters x = 6;, it looks up x. No mut flag? Error E0596. The check is purely syntactic at the binding level.

This approach gives you readability for free. If a function has twenty variables, the ones marked mut are the ones that change state. The rest are constants. This reduces cognitive load. You don't have to trace every assignment to see if a value might shift. The declaration tells you everything.

The compiler also uses mutability for optimization. Immutable values can be cached, hoisted, or eliminated. Mutable values require the compiler to assume they might change, which can limit some optimizations. Adding mut when you don't need it can make your code slower. Only mark variables as mutable when they actually mutate.

Realistic scenarios

Mutable bindings appear in destructuring, function arguments, and loop accumulators. Each case has a specific pattern for applying mut.

Destructuring patterns

When you extract values from a struct or enum, the new bindings are immutable unless you mark them. You can make some fields mutable and others not.

struct Config {
    level: i32,
    name: String,
}

fn main() {
    let config = Config { level: 1, name: "Player".to_string() };

    // `mut` on `level` allows mutation. `name` stays immutable.
    let Config { mut level, name } = config;

    level += 1; // Works: `level` is mutable.
    // name = "Admin".to_string(); // E0596: `name` is not mutable.
}

Mark the field, not the struct. Granular mutability keeps your code precise.

Function parameters

Function parameters are bindings too. If you want to mutate a parameter, add mut to the signature.

fn process(mut data: Vec<u8>) {
    // `mut` allows reassigning `data` or mutating it in place.
    data.push(0);
    data = vec![1, 2];
}

Convention aside: putting mut on function parameters is rare in idiomatic Rust. It signals that the function modifies the local copy, which is often a code smell. Prefer returning a new value or taking &mut T when you need to mutate the caller's data. Use mut on parameters only when profiling shows that reusing the local variable avoids expensive allocations.

Loop accumulators

Accumulators are the most common use for mut. You start with a zero value and update it in each iteration.

fn sum(values: &[i32]) -> i32 {
    let mut total = 0;
    for v in values {
        total += v;
    }
    total
}

The mut here is necessary. The loop body reassigns total repeatedly. Without mut, the first addition fails with E0596.

Pitfalls and related errors

E0596 often masks a deeper confusion about references. The most common trap is mixing up a mutable binding with a mutable reference.

Mutable binding vs mutable reference

A mutable binding lets you reassign the variable. It does not let you mutate the data behind an immutable reference.

fn main() {
    let mut vec = vec![1, 2, 3];

    // `first` is a mutable binding, but it holds an immutable reference.
    let mut first = &vec[0];

    // You can reassign `first` to point to a different element.
    first = &vec[1];

    // You cannot mutate the value behind the reference.
    // E0596: cannot borrow `*first` as mutable, as it is behind a `&` reference.
    // The compiler blocks this because the reference type is `&i32`, not `&mut i32`.
    *first = 99;
}

The error mentions *first. The dereference requires a mutable borrow, but first holds an immutable reference. Adding mut to first doesn't help. You need a mutable reference type.

fn main() {
    let mut vec = vec![1, 2, 3];

    // `first` holds a mutable reference.
    let mut first = &mut vec[0];
    *first = 99; // Works: `&mut i32` allows mutation.
}

A mutable binding doesn't make the data mutable. Check the type, not just the variable.

The source must be mutable too

If you create a mutable reference, the source value must be mutable. If you fix the reference but forget the source, E0596 appears on the source variable.

fn main() {
    let vec = vec![1, 2, 3]; // Immutable vector.

    // E0596: cannot borrow `vec[0]` as mutable, as it is not declared as mutable.
    let first = &mut vec[0];
}

The error points to vec[0]. The compiler is telling you that vec is immutable. Add mut to vec.

fn main() {
    let mut vec = vec![1, 2, 3];
    let first = &mut vec[0]; // Works.
}

Fixing E0596 by adding mut can trigger E0502 if you hold an immutable borrow elsewhere. The borrow checker enforces exclusive access for mutable references. If you have let r = &vec[0]; and then try let m = &mut vec[0];, you get E0502. Drop the immutable borrow before creating the mutable one.

Interior mutability

Sometimes you can't use mut. Maybe the type is behind an immutable reference, or you're implementing a trait that requires &self. In those cases, reach for interior mutability. Cell<T> and RefCell<T> allow mutation through immutable references.

use std::cell::Cell;

fn main() {
    // `c` is immutable, but `Cell` allows mutation.
    let c = Cell::new(5);
    c.set(6); // Works: `Cell` provides interior mutability.
    println!("{}", c.get());
}

Interior mutability breaks the binding rule but keeps the type safe. Use Cell for simple types like integers. Use RefCell for complex types where you need runtime borrow checking.

When mut is blocked, Cell slips through. Interior mutability is the escape hatch.

Decision matrix

Use let mut x when you plan to reassign x to a new value or call mutating methods on x. Use let x when the value never changes; the compiler will catch accidental mutations and improve readability. Use &mut T when you need to modify the data behind a reference; the reference type must be mutable, regardless of the binding. Use let mut r = &T when you need to update the reference itself to point to a different location, but the underlying data remains immutable. Use Cell<T> when you need to mutate a value through an immutable reference and the type supports interior mutability.

Where to go next