Rust 错误 E0596:cannot borrow as mutable, as it is not declared as mutable(忘了 mut)
E0596 的意思是:有段代码想要修改一个值(它需要可变借用,也就是 &mut),可你只给了它读取的权限。通常是 let 忘了加 mut,或者函数接收的是 &,而它其实需要 &mut。这个错误非常常见,编译器的 help: 行几乎总会准确告诉你该在哪里加 mut。本页先给出修法,再解释原因。这个系列的其他错误,请看 Rust 编译错误详解。
error[E0596]: cannot borrow `tasks` as mutable, as it is not declared as mutable --> src/main.rs:3:5 |3 | tasks.push("write tests"); // ❌ push 需要修改 `tasks` | ^^^^^ cannot borrow as mutable |help: consider changing this to be mutable |2 | let mut tasks = Vec::new(); | +++第一行的意思是“tasks 没有声明为可变,所以不能把它作为可变借用”。help 建议你“把它改成可变的”。
产生这个错误的程序是这样的:
fn main() { let tasks = Vec::new(); tasks.push("write tests"); // ❌ push 需要修改 `tasks` println!("{tasks:?}");}你还会见到另一种措辞:“cannot borrow *tasks as mutable, as it is behind a & reference”(*tasks 在 & 引用后面,不能作为可变借用)。错误代码一样,原因稍有不同:这时变量本身是一个 & 引用,所以你不能修改它指向的东西。下面两种情况都会讲到。
这个错误是什么意思
Section titled “这个错误是什么意思”看第 3 行 tasks 下面的 ^^^^^。要添加元素,Vec::push 需要这个 vector 的可变借用:它的签名是 fn push(&mut self, value: T)。但 tasks 是用普通的 let 创建的,所以它是只读的。你说过它永远不会变,Rust 就不能给它发一个 &mut。
help: 行给出了修法:let mut tasks。+++ 标出了要加上的三个字符。
想象一份设置成“仅查看”的共享文档。每个字你都能读,但“编辑”按钮是灰的。mut(或 &mut)就是你申请编辑权限的方式。
Rust 为什么要这样做
Section titled “Rust 为什么要这样做”在 Rust 里,变量默认不可变,引用默认只读。要修改任何东西,都需要明确开启:自己拥有的变量用 let mut,借来的东西用 &mut 引用。参见变量和借用。
这让代码更值得信任。如果一个函数接收 &Vec<String>,单看签名你就知道它不会改你的列表。如果它接收 &mut Vec<String>,你就知道它可能会改。没有什么会在你背后悄悄变化。
同一条规则也让 Rust 更大的安全保证成为可能。因为“能修改”总是明明白白写出来的,编译器就能确保写入者和读取者永远不会同时存在(那是 E0502 和 E0499)。E0596 是第一步:在检查写入是否安全之前,你得先申请写入。
修复 1:给变量加上 mut
Section titled “修复 1:给变量加上 mut”当值归你所有、而且需要修改时,就用这个方法:要往里 push 的 Vec、要追加内容的 String,或者要调用 &mut self 方法的结构体。大多数情况下这都是正确的修法。
struct Counter { count: u32,}
impl Counter { fn increment(&mut self) { self.count += 1; }}
fn main() { let mut tasks = Vec::new(); tasks.push("写测试"); tasks.push("修复 bug"); println!("{tasks:?}");
let mut counter = Counter { count: 0 }; counter.increment(); counter.increment(); println!("计数:{}", counter.count);}输出:
["写测试", "修复 bug"]计数:2如果 counter 没有 mut,counter.increment() 会报 cannot borrow counter as mutable。调用接收 &mut self 的方法,会把整个变量可变地借出去,所以这个变量必须是 mut 的。只接收 &self 的方法,不加 mut 也能正常调用。参见用方法添加行为。
函数自己拥有的参数也是一样:fn shout(s: String) 里不能调用 s.push('!'),但 fn shout(mut s: String) 可以。
修复 2:把参数改成 &mut,并传入 &mut x
Section titled “修复 2:把参数改成 &mut,并传入 &mut x”当函数需要修改调用方拥有的东西时,就用这个方法。下面这个版本会给出 “behind a & reference” 的那种措辞:
fn add_task(tasks: &Vec<String>, task: &str) { tasks.push(task.to_string()); // ❌ `tasks` 是 `&` 引用}error[E0596]: cannot borrow `*tasks` as mutable, as it is behind a `&` reference --> src/main.rs:2:5 |2 | tasks.push(task.to_string()); // ❌ `tasks` 是 `&` 引用 | ^^^^^ `tasks` is a `&` reference, so it cannot be borrowed as mutable |help: consider changing this to be a mutable reference |1 | fn add_task(tasks: &mut Vec<String>, task: &str) { | +++标签说的是“tasks 是 & 引用,所以不能作为可变借用”,help 建议把它改成可变引用。要改三个地方:参数类型改成 &mut Vec<String>,调用方的变量改成 let mut,调用时传入 &mut tasks:
fn add_task(tasks: &mut Vec<String>, task: &str) { tasks.push(task.to_string());}
fn main() { let mut tasks = Vec::new(); add_task(&mut tasks, "写测试"); add_task(&mut tasks, "更新文档"); println!("{} 项任务:{tasks:?}", tasks.len());}输出:
2 项任务:["写测试", "更新文档"]如果你改了参数,调用时却还是传 &tasks,就会得到 E0308,并附带提示 types differ in mutability(类型的可变性不同)。意思就是:调用的地方也要写 &mut tasks。在调用处写 &mut 是有意这样设计的,任何人读 main 都能看出 add_task 可能会修改 tasks。
修复 3:把闭包变量声明为 mut
Section titled “修复 3:把闭包变量声明为 mut”当闭包修改了它捕获的变量时,就用这个方法。这样的闭包每次运行都会改变自身的状态,所以调用它需要对闭包本身进行可变借用:
fn main() { let mut clicks = 0; let click = || clicks += 1; click(); // ❌ 不能把 `click` 作为可变借用 click(); println!("Clicks: {clicks}");}error[E0596]: cannot borrow `click` as mutable, as it is not declared as mutable --> src/main.rs:3:9 |3 | let click = || clicks += 1; | ^^^^^ ------ calling `click` requires mutable binding due to mutable borrow of `clicks` | | | not mutable4 | click(); // ❌ 不能把 `click` 作为可变借用 | ----- cannot borrow as mutable5 | click(); | ----- cannot borrow as mutable |help: consider changing this to be mutable |3 | let mut click = || clicks += 1; | +++rustc 解释说:因为闭包可变地借用了 clicks,所以调用 click 需要一个可变的绑定。变量 clicks 已经是 mut 了,缺的是闭包上的 mut:
fn main() { let mut clicks = 0; let mut click = || clicks += 1; click(); click(); click(); println!("点击次数:{clicks}");}输出:
点击次数:3很多人会对此感到意外,因为这个闭包“看起来”并没有在变。但它内部持有一个 &mut clicks,所以它是一个 FnMut 闭包,而调用 FnMut 需要 mut。参见修改捕获变量的闭包和 Fn、FnMut 与 FnOnce。
修复 4:在循环里用 iter_mut() 或 &mut
Section titled “修复 4:在循环里用 iter_mut() 或 &mut”当你遍历一个集合、想修改每个元素时,就用这个方法。.iter() 和 for x in &list 给出的是只读的 & 引用:
for name in names.iter() { name.push('!'); // ❌ cannot borrow `*name` as mutable, as it is behind a `&` reference}换成 .iter_mut() 或 &mut names(并且 names 本身也要是 mut 的):
fn main() { let mut names = vec![String::from("ferris"), String::from("corro")]; for name in names.iter_mut() { name.push('!'); } println!("{names:?}");
let mut prices = vec![10, 20, 30]; for price in &mut prices { *price += 5; } println!("{prices:?}");}输出:
["ferris!", "corro!"][15, 25, 35]编译器也会这样建议:help: use mutable method ... names.iter_mut()(改用可变的方法)。迭代器链里的闭包也是一样:words.iter().for_each(|w| w.push('!')) 会失败,words.iter_mut().for_each(|w| w.push('!')) 就可以。参见迭代器。
一步步构建 Vec 或 String。 先 let list = Vec::new(); 再 list.push(...),或者先 let s = String::new(); 再 s.push_str(...)。修复:let mut。如果一开始就有全部元素,let list = vec![a, b, c]; 根本不需要 mut。
在结构体上调用 “setter” 或 “update” 方法。 如果 deposit 接收 &mut self,那么除非你写了 let mut account,否则 account.deposit(50) 会失败。修复:let mut account = Account::new();。参见结构体。
出于习惯写成接收 & 的辅助函数。 你写了 fn reset(board: &Board),然后又想在里面修改 board。修复:改成 board: &mut Board,并调用 reset(&mut board)。一个好习惯:函数只读就写 &,要修改就写 &mut。
修改 HashMap 里的值。 if let Some(v) = map.get("key") { v.push(..) } 会失败,因为 get 返回的是 & 引用。修复:用 map.get_mut("key"),或者用 entry API。参见哈希 map。
- E0384:cannot assign twice to immutable variable(给不可变变量二次赋值)。同样是缺了
mut,只不过出现在x = ...或x += 1上,而不是方法调用上。 - E0594:不能给字段赋值,或者不能通过
&引用赋值,比如p不是mut时写p.hp = 5;,或者在for x in &v里写*x += 1。修法相同:let mut,或者&mut。 - E0308:类型不匹配,
types differ in mutability,出现在你把&x传给需要&mut x的函数时。 - E0502:你拿到了
&mut权限,但还有别的代码在读这个值。 - E0499:同一个值同时有两个
&mut借用。 - 所有编译错误:错误页面的完整列表。
小测验
1. let scores = Vec::new(); scores.push(10); 报了 E0596。该怎么改?
2. 函数 fn add(list: &Vec<u32>) 里调用了 list.push(1)。你要改什么?
3. let mut total = 0; let add = |x| total += x; add(5); 编译失败。为什么?
得分: 0 / 3
- E0596 — Rust error codes index(英文):这个错误的官方说明。
- 引用与借用 — Rust 程序设计语言:其中“可变引用”一节讲解了
&mut,以及为什么必须主动申请它。 - 方法语法 — Rust 程序设计语言:
&self和&mut self的区别,以及它们各自对调用方有什么要求。 - 闭包:可以捕获其环境的匿名函数 — Rust 程序设计语言:闭包怎样捕获变量,以及会修改状态的闭包为什么必须是
mut的。 - 可变性 — 通过例子学 Rust:把
&和&mut借用放在一起对比的可运行示例。