Rust 错误 E0384:cannot assign twice to immutable variable(给不可变变量二次赋值)
E0384 的意思是:你给一个变量赋了值,接着又想给它另一个值,可这个变量声明时没有加 mut。在 Rust 里,除非你明确说要改,否则变量是不能修改的。几乎每个人在学 Rust 的第一个小时里都会碰到它,而修复通常只需要三个字母:mut。本页先给出修法,再解释 Rust 为什么这样设计。这个系列的其他错误,请看 Rust 编译错误详解。
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 没有声明 mut | ^^^^^^^^^^ cannot assign twice to immutable variable |help: consider making this binding mutable |2 | let mut score = 10; | +++第一行的意思是“不能给不可变变量 score 赋值两次”。help 建议你“把这个绑定改成可变的”。
产生这个错误的程序是这样的:
fn main() { let score = 10; println!("Score: {score}"); score = 15; // ❌ score 没有声明 mut println!("New score: {score}");}如果这个变量是函数参数,措辞会稍有不同:“cannot assign to immutable argument”(不能给不可变参数赋值)。错误代码一样,道理也一样。
这个错误是什么意思
Section titled “这个错误是什么意思”编译器指出了两个位置:
- 第 2 行,标着
first assignment to score(第一次给score赋值):score在这里得到了它的值。因为这一行写的是let而不是let mut,这个值就定下来了。 - 第 4 行,用
^^^^标出:你在这里想放进一个新值,Rust 拒绝了。
help: 部分已经给出了修法:let mut score = 10;。+++ 标出了要加上的三个字符。
可以把 let 想成用钢笔写字,把 let mut 想成用铅笔写字。两种都没问题,但用钢笔写下的东西,之后就擦不掉了。
同样的规则也适用于 +=、-=、*= 这类运算符。count += 1 也是一次赋值,所以同样需要 mut。
Rust 为什么要这样做
Section titled “Rust 为什么要这样做”在 Rust 里,所有变量默认都是不可变的。想要修改,必须用 mut 主动开启。变量那一页讲过这一点。
这听起来很严格,但对你帮助很大。读到 let total = ... 时,你就知道 total 会一直保持这个值,直到作用域结束。你不用把函数剩下的部分翻一遍,确认有没有什么地方改了它。而看到 let mut,就是一个清楚的信号:“注意这个,它会变”。
它还能抓出真正的 bug。如果你本想修改一个副本,却不小心写到了原来的变量上,或者不小心重复用了一个名字,编译器都会拦住你。而且对于从来没有被修改过的 mut 变量,Rust 也会发出警告(variable does not need to be mutable,变量不需要是可变的),所以这个信号在两个方向上都是可信的。
修复 1:加上 mut
Section titled “修复 1:加上 mut”当变量确实需要随时间变化时,就用这个方法:计数器、累计总数、分数、状态等。大多数情况下这都是正确的修法,也正是编译器的建议。
fn main() { let mut score = 10; println!("分数:{score}"); score = 15; // ✅ 现在可以了 println!("新分数:{score}");
let mut count = 0; for _ in 0..3 { count += 1; } println!("计数:{count}");}输出:
分数:10新分数:15计数:3计数器是最典型的例子:先写 let count = 0;,再在循环里写 count += 1;,就会在 += 那一行得到 E0384。加上 mut 就好了。参见用 mut 让变量可以修改。
修复 2:用遮蔽得到一个新值
Section titled “修复 2:用遮蔽得到一个新值”当你其实并不想修改这个变量时,就用这个方法。你想要的是一个从旧值算出来的新值,往往要分几步算,算完之后就不再变了。用同一个名字再写一次 let:
fn main() { let price = 20; let price = price + 5; // 一个新的 `price`,由旧的算出来 let price = price * 2; println!("最终价格:{price}");
let input = " 42 "; let input = input.trim(); // 仍然是文本 let input: u32 = input.parse().unwrap(); // 现在是数字了 println!("解析结果:{}", input + 1);}输出:
最终价格:50解析结果:43这叫做遮蔽(shadowing):每个 let 都会创建一个全新的变量,把旧的那个遮住。这样做有两个好处。最后的 price 是不可变的,之后不会被意外修改。而且类型可以改变,比如 input 从 &str 变成了 u32,这是 mut 做不到的。更多内容见遮蔽:重复使用同一个名字。
一个简单的规则:如果值会一次又一次地变化(在循环里、随着时间),就用 mut。如果你只是在分几步准备一个值,用遮蔽读起来更清楚。
修复 3:给函数参数加上 mut
Section titled “修复 3:给函数参数加上 mut”当函数想修改它自己那份参数时,就用这个方法。参数默认也是不可变的,所以下面的代码会失败:
fn countdown(n: u32) { while n > 0 { println!("{n}..."); n -= 1; // ❌ 参数 n 不可变 } println!("Liftoff!");}error[E0384]: cannot assign to immutable argument `n` --> src/main.rs:4:9 |4 | n -= 1; // ❌ 参数 n 不可变 | ^^^^^^ cannot assign to immutable argument |help: consider making this binding mutable |1 | fn countdown(mut n: u32) { | +++在参数名前面加上 mut:
fn countdown(mut n: u32) { while n > 0 { println!("{n}..."); n -= 1; } println!("发射!");}
fn main() { let start = 3; countdown(start); println!("start 仍然是 {start}");}输出:
3...2...1...发射!start 仍然是 3注意,main 里的 start 并没有变。mut n 只是让函数自己那份副本可以修改;调用方不需要 mut,也不受影响。如果你想让函数修改调用方的变量,就改成接收 &mut u32,并传入 &mut start。参见函数和借用。
修改 for 循环变量。 每一轮循环,循环变量都是一个全新的不可变变量:
for i in 0..5 { if i == 2 { i += 1; // ❌ cannot assign twice to immutable variable `i` } println!("{i}");}修复:你很少真的需要修改循环计数器本身。可以用 continue 跳过,用 .step_by(2),或者如果需要完全掌控,改用带 let mut i 的 while 循环。(for mut i in 0..5 能编译,但它只会修改这一轮的副本,并不会跳过任何东西。)参见 break 和 continue 以及 while 循环。
先声明、后赋值。 不带值的 let total; 是允许的,你可以给它赋值一次。第二次赋值就会得到 E0384:
let total;total = 5;total = 6; // ❌ cannot assign twice to immutable variable `total`修复:如果它真的会变,就用 let mut total。如果你只是想从几个值里选一个,就用一个表达式算出来:let total = if big_order { 6 } else { 5 };。参见 if / else。
累加总数。 先写 let sum = 0;,再在循环里写 sum += price;,这是最常见的 E0384。修复:let mut sum = 0;,或者干脆不要这个变量,写成 let sum: u32 = prices.iter().sum();。参见迭代器。
- E0596:cannot borrow as mutable, as it is not declared as mutable(变量没有声明为可变,不能可变借用)。同样是“忘了
mut”,只不过出现在调用.push()这类会修改值的方法时,而不是赋值时。 - E0594:cannot assign to
p.hp, aspis not declared as mutable。当你给结构体字段赋值(p.hp = 5;)或通过&引用写入时会看到它。修复:let mut p,或者用&mut引用。参见让结构体可变。 - E0502:值正被借用来读取时,你又想修改它。
- E0382:值被移动之后,你还在使用它。
- 所有编译错误:错误页面的完整列表。
小测验
1. 你写了 let lives = 3;,后来又写了 lives -= 1;。为什么会得到 E0384?
2. 你想把 name 整理一次(去掉首尾空格并转成大写),之后再也不改它。哪种写法最清楚?
3. fn halve(n: u32) 里写了 n /= 2;,报了 E0384。不影响调用方的修法是什么?
得分: 0 / 3
- E0384 — Rust error codes index(英文):这个错误的官方说明,附有示例。
- 变量与可变性 — Rust 程序设计语言:讲解
let、mut、常量和遮蔽的章节。 - 可变变量 — 通过例子学 Rust:一个对比
let和let mut的简短可运行示例。 - 冻结 — 通过例子学 Rust:用不可变变量遮蔽可变变量,怎样把它“冻结”起来。