Rust Error E0594: Cannot Assign, as It Is Not Declared as Mutable
E0594 means you wrote something = value (or +=, -=, …), but you only have permission to read that something. Usually it’s a struct field on a variable without mut, or a value you reach through a & reference. It’s a very common error, and the fix is almost always one word: mut. This page shows the fix first, then explains it. For the other errors in this series, see Rust compiler errors.
The error message
Section titled “The error message”error[E0594]: cannot assign to `p.hp`, as `p` is not declared as mutable --> src/main.rs:8:5 |8 | p.hp = 5; // ❌ `p` is read-only | ^^^^^^^^ cannot assign |help: consider changing this to be mutable |7 | let mut p = Player { name: String::from("Ferris"), hp: 10 }; | +++This is the program that produces it:
struct Player { name: String, hp: u32,}
fn main() { let p = Player { name: String::from("Ferris"), hp: 10 }; p.hp = 5; // ❌ `p` is read-only println!("{} has {} hp", p.name, p.hp);}You will also often see a second wording: “cannot assign to *x, which is behind a & reference”. Same error code, slightly different cause: there you hold a & reference, and a & reference only lets you look. Both are covered below.
What it means
Section titled “What it means”Look at the ^^^^^^^^ under p.hp = 5 on line 8. You are changing a field of p. But p was created with plain let, so the whole struct is read-only, and that includes every field inside it.
The help: line shows the fix: let mut p. The +++ marks the three characters to add.
With the second wording, the compiler points at a line like *x += 1 and adds `x` is a `&` reference, so it cannot be written to. You are not changing a variable you own. You are trying to change someone else’s value through a borrowed, read-only reference.
Think of a library book. You can read it as much as you like, but you’re not allowed to write in it. To write notes, you need your own copy (let mut) or the owner’s permission (&mut).
Why Rust does this
Section titled “Why Rust does this”In Rust, everything is read-only unless you say otherwise. A variable needs let mut to change, and a reference needs to be &mut to change what it points to. See Making a struct mutable and Mutable references.
There is no “mutable field” in Rust. Mutability belongs to the variable (or the reference), not to single fields. So let mut p makes every field of p changeable, and plain let p makes none of them changeable. That keeps the rule simple: look at how a value was declared and you know whether it can change.
The reference rule protects you from surprises. If you pass &player to a function, its signature promises it will only read. Without that promise, any function could quietly change your data, and the compiler couldn’t stop a reader and a writer from touching the same value at the same time (that’s what E0502 is about).
How to fix it
Section titled “How to fix it”Fix 1: Add mut to the variable
Section titled “Fix 1: Add mut to the variable”Choose this when you own the struct and want to change its fields. This is the right fix most of the time.
struct Player { name: String, hp: u32,}
fn main() { let mut p = Player { name: String::from("Ferris"), hp: 10 }; p.hp = 5; p.hp += 3; println!("{} has {} hp", p.name, p.hp);}Output:
Ferris has 8 hpThe same goes for tuples (pair.0 = 1;) and arrays (grid[2] = 7;): the variable holding them needs mut. For a struct inside a Vec, team[0].hp = 10; needs let mut team; that case is reported as E0596, but the fix is the same.
If you only assign once, you may not need mut at all: build the struct with the right value from the start, like Player { hp: 5, ..other }.
Fix 2: Loop with &mut v or iter_mut()
Section titled “Fix 2: Loop with &mut v or iter_mut()”Choose this when you loop over a collection and want to change each item. for x in &v and v.iter() hand out & references, which are read-only:
fn main() { let scores = vec![10, 20, 30]; for x in &scores { *x += 1; // ❌ `x` is a `&i32` } println!("{scores:?}");}error[E0594]: cannot assign to `*x`, which is behind a `&` reference --> src/main.rs:4:9 |3 | for x in &scores { | ------- this iterator yields `&` references4 | *x += 1; // ❌ `x` is a `&i32` | ^^^^^^^ `x` is a `&` reference, so it cannot be written to |help: use a mutable iterator instead |3 | for x in &mut scores { | +++Loop over &mut scores (or call .iter_mut()), and make the vector itself mut:
fn main() { let mut scores = vec![10, 20, 30]; for x in &mut scores { *x += 1; } println!("{scores:?}");
for x in scores.iter_mut() { *x *= 2; } println!("{scores:?}");}Output:
[11, 21, 31][22, 42, 62]&mut scores and scores.iter_mut() do the same thing; pick whichever reads better. If you don’t need to change the list in place, building a new one also works: let bigger: Vec<i32> = scores.iter().map(|x| x + 1).collect();. See Iterators.
Fix 3: Use &mut self or a &mut parameter
Section titled “Fix 3: Use &mut self or a &mut parameter”Choose this when a method or function changes data it was given. A method with &self can only read the struct, so assigning to a field fails:
impl Account { fn deposit(&self, amount: u64) { self.balance += amount; // ❌ `self` is a `&` reference }}error[E0594]: cannot assign to `self.balance`, which is behind a `&` reference --> src/main.rs:7:9 |7 | self.balance += amount; | ^^^^^^^^^^^^^^^^^^^^^^ `self` is a `&` reference, so it cannot be written to |help: consider changing this to be a mutable reference |6 | fn deposit(&mut self, amount: u64) { | +++A plain function with a & parameter gives the same error: fn reset(score: &u32) { *score = 0; } fails with cannot assign to `*score`, which is behind a `&` reference. The fix is the same for both: say &mut in the signature, make the caller’s variable let mut, and pass &mut at the call.
struct Account { balance: u64,}
impl Account { fn deposit(&mut self, amount: u64) { self.balance += amount; }}
fn reset(score: &mut u32) { *score = 0;}
fn main() { let mut acc = Account { balance: 0 }; acc.deposit(50); acc.deposit(25); println!("Balance: {}", acc.balance);
let mut score = 42; reset(&mut score); println!("Score: {score}");}Output:
Balance: 75Score: 0For methods, Rust writes the &mut for you at the call: acc.deposit(50) borrows acc mutably on its own, as long as acc is let mut. For functions you write it yourself: reset(&mut score). If you pass &score to a function that wants &mut u32, you get E0308 (types differ in mutability). See Adding behavior with methods.
Fix 4 (last resort): Cell or RefCell
Section titled “Fix 4 (last resort): Cell or RefCell”Choose this only when you can’t get &mut: for example, you implement a trait whose method is fixed as &self, or many parts of the program share one value through Rc. Cell and RefCell allow changes through a & reference (“interior mutability”):
use std::cell::{Cell, RefCell};
struct Catalog { items: Vec<String>, lookups: Cell<u32>, history: RefCell<Vec<String>>,}
impl Catalog { fn contains(&self, name: &str) -> bool { self.lookups.set(self.lookups.get() + 1); self.history.borrow_mut().push(name.to_string()); self.items.iter().any(|item| item == name) }}
fn main() { let catalog = Catalog { items: vec![String::from("apple"), String::from("pear")], lookups: Cell::new(0), history: RefCell::new(Vec::new()), }; println!("pear? {}", catalog.contains("pear")); println!("kiwi? {}", catalog.contains("kiwi")); println!("Lookups: {}", catalog.lookups.get()); println!("History: {:?}", catalog.history.borrow());}Output:
pear? truekiwi? falseLookups: 2History: ["pear", "kiwi"]Cell is for small Copy values like counters; RefCell is for anything else. Be honest about the cost: RefCell moves the borrow check from compile time to run time, and it panics if you borrow mutably twice at once. If plain &mut works, prefer it. Across threads, use Mutex or atomics instead (see Shared state). More in RefCell.
Where you’ll usually see it
Section titled “Where you’ll usually see it”Changing a value inside a HashMap. *stock.get("apple").unwrap() += 1; fails with cannot assign to data in a `&` reference, because get returns a & reference. Fix: *stock.get_mut("apple").unwrap() += 1;, or *stock.entry("apple").or_insert(0) += 1;. See HashMaps.
A closure passed as Fn that changes a variable. run(|| count += 1) where run takes F: Fn() fails with cannot assign to `count`, as it is a captured variable in a `Fn` closure. Fix: change the bound to F: FnMut() (and mut f inside run). See When you pick the wrong bound.
A global counter in a static. static VISITS: u32 = 0; then VISITS += 1; fails with cannot assign to immutable static item `VISITS`. Fix: use static VISITS: AtomicU32 = AtomicU32::new(0); and VISITS.fetch_add(1, Ordering::Relaxed);, or better, pass a counter around as &mut.
Related errors
Section titled “Related errors”- E0596: cannot borrow as mutable. The same missing
mut, but for a method call likelist.push(x)instead of an assignment. - E0384: cannot assign twice to immutable variable. You assign to a whole variable (
x = 5;) instead of a field or through a reference. - E0308: mismatched types,
types differ in mutability, when you pass&xwhere&mut xis expected. - E0502: you have
&mutaccess, but something else is still reading the value. - E0507: cannot move out of a value behind a reference. The “read-only” rule again, this time for moves instead of writes.
- All compiler errors: the full list of error pages.
Quick check
Section titled “Quick check”Quick check
1. let user = User { age: 30 }; user.age = 31; gives E0594. What is the fix?
2. for p in &prices { *p += 1; } fails. What do you change?
3. A method fn rename(&self, n: String) { self.name = n; } gives E0594. What is the usual fix?
Score: 0 / 3
Read more
Section titled “Read more”- E0594 — Rust error codes index: the official explanation of this error.
- Defining and Instantiating Structs — The Rust Book: why the whole instance must be mutable, not single fields.
- References and Borrowing — The Rust Book: the “Mutable References” part explains
&versus&mut. - Method Syntax — The Rust Book: when a method needs
&self,&mut self, orself. - RefCell and Interior Mutability — The Rust Book: how
RefCellallows changes through&, and its run-time cost.