Skip to content

Rust Error E0384: Cannot Assign Twice to Immutable Variable

E0384 means you gave a variable a value and then tried to give it another value, but the variable was not declared with mut. In Rust, variables can’t be changed unless you say so. Almost everyone hits this in their first hour of Rust, and the fix is usually three letters: mut. This page shows the fix first, then explains why Rust works this way. For the other errors in this series, see Rust compiler errors.

error[E0384]: cannot assign twice to immutable variable `score`
--> src/main.rs:4:5
|
2 | let score = 10;
| ----- first assignment to `score`
3 | println!("Score: {score}");
4 | score = 15; // ❌ score is not mut
| ^^^^^^^^^^ cannot assign twice to immutable variable
|
help: consider making this binding mutable
|
2 | let mut score = 10;
| +++

This is the program that produces it:

fn main() {
let score = 10;
println!("Score: {score}");
score = 15; // ❌ score is not mut
println!("New score: {score}");
}

If the variable is a function parameter, the wording changes a little to “cannot assign to immutable argument”. It is the same error code and the same idea.

The compiler points at two places:

  1. Line 2, marked first assignment to score: this is where score got its value. Because the line says let and not let mut, that value is final.
  2. Line 4, marked with ^^^^: this is where you tried to put a new value in. Rust refuses.

The help: part already shows the fix: let mut score = 10;. The +++ marks the three characters to add.

Think of let as writing in pen and let mut as writing in pencil. Both are fine. But if you wrote in pen, you can’t erase it later.

The same rule applies to +=, -=, *= and friends. count += 1 is an assignment too, so it also needs mut.

In Rust, every variable is immutable by default. You have to opt in to change with mut. This is explained on the Variables page.

This sounds strict, but it helps you a lot. When you read let total = ..., you know total keeps that value until the end of its scope. You don’t have to scan the rest of the function to check if something changed it. When you do see let mut, it’s a clear signal: “watch this one, it changes”.

It also catches real bugs. If you meant to change a copy but accidentally wrote to the original, or you reused a name by mistake, the compiler stops you. And Rust warns you about mut variables that never change (variable does not need to be mutable), so the signal stays honest in both directions.

Choose this when the variable really should change over time: a counter, a running total, a score, a state. This is the right fix most of the time, and it’s exactly what the compiler suggests.

fn main() {
let mut score = 10;
println!("Score: {score}");
score = 15; // ✅ allowed now
println!("New score: {score}");
let mut count = 0;
for _ in 0..3 {
count += 1;
}
println!("Count: {count}");
}

Output:

Score: 10
New score: 15
Count: 3
▶ Try it Yourself

The counter is the classic case: let count = 0; followed by count += 1; inside a loop gives E0384 on the += line. Adding mut fixes it. See Making a variable changeable with mut.

Choose this when you don’t really want to change the variable. You want a new value that is computed from the old one, often in a few steps, and after that it stays the same. Write let again with the same name:

fn main() {
let price = 20;
let price = price + 5; // a new `price`, built from the old one
let price = price * 2;
println!("Final price: {price}");
let input = " 42 ";
let input = input.trim(); // still text
let input: u32 = input.parse().unwrap(); // now a number
println!("Parsed: {}", input + 1);
}

Output:

Final price: 50
Parsed: 43
▶ Try it Yourself

This is called shadowing: each let creates a brand-new variable that hides the old one. Two nice things come with it. The final price is immutable, so nothing can change it by accident later. And the type can change, like input going from &str to u32, which mut can’t do. More on this in Shadowing: reusing the same name.

A simple rule: if the value changes again and again (in a loop, over time), use mut. If you’re preparing a value in a few steps, shadowing reads nicely.

Choose this when a function wants to change its own copy of an argument. Parameters are immutable too, so this fails:

fn countdown(n: u32) {
while n > 0 {
println!("{n}...");
n -= 1; // ❌ cannot assign to immutable argument `n`
}
println!("Liftoff!");
}
error[E0384]: cannot assign to immutable argument `n`
--> src/main.rs:4:9
|
4 | n -= 1; // ❌ cannot assign to immutable argument `n`
| ^^^^^^ cannot assign to immutable argument
|
help: consider making this binding mutable
|
1 | fn countdown(mut n: u32) {
| +++

Put mut before the parameter name:

fn countdown(mut n: u32) {
while n > 0 {
println!("{n}...");
n -= 1;
}
println!("Liftoff!");
}
fn main() {
let start = 3;
countdown(start);
println!("start is still {start}");
}

Output:

3...
2...
1...
Liftoff!
start is still 3
▶ Try it Yourself

Notice that start in main didn’t change. mut n only makes the function’s own copy changeable; the caller doesn’t need mut and isn’t affected. If you want the function to change the caller’s variable, take a &mut u32 instead and pass &mut start. See Functions and Borrowing.

Changing a for loop variable. The loop variable is a fresh, immutable variable on every pass:

for i in 0..5 {
if i == 2 {
i += 1; // ❌ cannot assign twice to immutable variable `i`
}
println!("{i}");
}

Fix: you rarely want to change the loop counter itself. Skip ahead with continue, use .step_by(2), or, if you need full control, switch to a while loop with a let mut i. (for mut i in 0..5 compiles, but it only changes this pass’s copy; it doesn’t skip anything.) See break and continue and while loops.

Declaring first, assigning later. let total; without a value is allowed, and you can assign it once. A second assignment gives E0384:

let total;
total = 5;
total = 6; // ❌ cannot assign twice to immutable variable `total`

Fix: if it really changes, use let mut total. If you only meant to pick one value, compute it in one expression: let total = if big_order { 6 } else { 5 };. See if / else.

Accumulating a total. let sum = 0; followed by sum += price; in a loop is the most common E0384 of all. Fix: let mut sum = 0;, or skip the variable and write let sum: u32 = prices.iter().sum();. See Iterators.

  • E0596: cannot borrow as mutable, as it is not declared as mutable. The same “you forgot mut” problem, but for calling a changing method like .push() instead of assigning.
  • E0594: cannot assign to p.hp, as p is not declared as mutable. You see this when you assign to a struct field (p.hp = 5;) or write through a & reference. Fix: let mut p, or a &mut reference. See Making a struct mutable.
  • E0502: you tried to change a value while it was borrowed for reading.
  • E0382: you used a value after it was moved.
  • All compiler errors: the full list of error pages.

Quick check

1. You write let lives = 3; and later lives -= 1;. Why do you get E0384?

2. You want to clean up name once (trim it and make it uppercase) and never change it again. What reads best?

3. fn halve(n: u32) does n /= 2; and gets E0384. What is the fix that keeps the caller unchanged?

Score: 0 / 3