Rust 错误 E0499:cannot borrow as mutable more than once at a time(两个可变借用)
E0499 的意思是:你想同时拥有同一个值的两个可变引用(&mut)。这是借用检查器最常见的错误之一,修复起来通常不费事:很多时候只要挪动一行代码就行。(想找别的错误?请看编译错误总览。)
error[E0499]: cannot borrow `balance` as mutable more than once at a time --> src/main.rs:5:20 |4 | let deposit = &mut balance; | ------------ first mutable borrow occurs here5 | let withdraw = &mut balance; | ^^^^^^^^^^^^ second mutable borrow occurs here6 |7 | *deposit += 50; | -------------- first borrow later used here产生这个错误的程序是这样的:
fn main() { let mut balance = 100;
let deposit = &mut balance; let withdraw = &mut balance;
*deposit += 50; *withdraw -= 30;
println!("Balance: {balance}");}编译器说:同一时间不能把 balance 可变地借用多次(“cannot borrow balance as mutable more than once at a time”)。三条标注分别是:第一个可变借用发生的位置(first mutable borrow occurs here)、第二个可变借用发生的位置(second mutable borrow occurs here),以及之后又用到第一个借用的位置(first borrow later used here)。
这个错误是什么意思
Section titled “这个错误是什么意思”编译器指出了三个地方,合起来就是整个故事:
- 第 4 行:
deposit成为balance的一个可变借用。 - 第 5 行:
withdraw想成为balance的第二个可变借用。 - 第 7 行:在第 5 行之后,
deposit还在被使用。所以第二个借用开始时,第一个借用还活着。
第三条标注最关键。两个 &mut 借用只有在生命周期重叠时才会出问题。如果 deposit 在第 5 行之前就用完了,就不会报错。
想象一份共享文档,只有一把“编辑”钥匙。谁拿着钥匙,谁就能修改文档。Rust 不会给钥匙配第二把:第二个人得等第一个人把钥匙还回来。
Rust 为什么要这样做
Section titled “Rust 为什么要这样做”Rust 的借用规则是:任何时刻,你可以有很多个读者(&),或者一个写者(&mut),但写者绝不能超过一个。&mut 引用是一个承诺:“只要我还在,别人就不能碰这个值。”
这个承诺能防止真实的 bug。如果两段代码能同时修改同一个值,其中一段可能会让 Vec 扩容,而另一段还指向它的旧内存。在别的语言里,这会导致崩溃,或者数据被悄悄破坏。放到多个线程之间,这就是数据竞争(data race)。
有了这条规则,编译器就能确信:在 &mut 存在期间,它是访问这份数据的唯一途径。这份安全性没有任何运行时开销。代价是,有时你得更清楚地告诉编译器,你的两个借用其实并不冲突。
修复 1:先结束第一个借用,再开始第二个
Section titled “修复 1:先结束第一个借用,再开始第二个”首选这个方法。 大多数 E0499 错误都能这样解决。借用一直持续到它最后一次使用,而不是到代码块结束。所以先用第一个引用把事情做完,再创建第二个:
fn main() { let mut balance = 100;
let deposit = &mut balance; *deposit += 50; // 最后一次使用 `deposit`:它的借用到这里结束
let withdraw = &mut balance; // ✅ 现在没有别人在借用了 *withdraw -= 30;
println!("余额:{balance}");}输出:
余额:120很多时候你根本不需要这些额外的变量:先写 balance += 50; 再写 balance -= 30;,效果完全一样。
修复 2:借用互不重叠的部分
Section titled “修复 2:借用互不重叠的部分”有时你确实需要同时拿着两个 &mut,但它们指向数据的不同部分。编译器不一定看得出来,所以你得明确告诉它。
对于 Vec 或 slice 里的两个元素,tasks[0] 和 tasks[1] 都会借用整个 tasks。用 split_at_mut(英文)把它切成两半,保证两半互不重叠:
fn main() { let mut tasks = vec![String::from("write"), String::from("test")];
// 把 Vec 切成互不重叠的两半 let (left, right) = tasks.split_at_mut(1); let first = &mut left[0]; let second = &mut right[0];
std::mem::swap(first, second); println!("{tasks:?}");}输出:
["test", "write"](如果只是交换,tasks.swap(0, 1) 更简单。split_at_mut 适合真的需要用两个引用做事的场合。)
对于结构体,当你调用两个都接收 &mut self 的方法时(比如 p.name_mut() 和 p.score_mut()),也会出现同样的错误。每次调用都会借用整个结构体。如果改成直接借用字段,编译器就能看出它们是分开的:
struct Player { name: String, score: u32,}
fn main() { let mut p = Player { name: String::from("Ferris"), score: 0 };
// 直接借用字段,而不是通过方法 let name = &mut p.name; let score = &mut p.score;
name.push_str(" 小螃蟹"); *score += 10; println!("{} 有 {} 分", p.name, p.score);}输出:
Ferris 小螃蟹 有 10 分修复 3:用索引,而不是一直拿着引用
Section titled “修复 3:用索引,而不是一直拿着引用”一个经典场景:你用 iter_mut() 遍历一个 Vec,又想在循环里往它里面 push。迭代器在整个循环期间都拿着一个 &mut,所以 push 就成了第二个:
fn main() { let mut queue = vec![1, 2, 3]; for job in queue.iter_mut() { *job *= 10; queue.push(*job); // ❌ E0499: `queue` is already borrowed by iter_mut() }}当循环必须修改集合本身时,就改为遍历位置。索引只是一个数字,它什么都不借用:
fn main() { let mut queue = vec![1, 2, 3];
// 遍历位置,而不是拿着一个指向 `queue` 内部的引用 for i in 0..queue.len() { queue[i] *= 10; let job = queue[i]; queue.push(job); }
println!("{queue:?}");}输出:
[10, 20, 30, 10, 20, 30]0..queue.len() 在循环开始之前就算好了,只算一次,所以新加的元素不会再被遍历。每个 queue[i] 借用只持续一行。
修复 4:RefCell(最后的手段)
Section titled “修复 4:RefCell(最后的手段)”RefCell 把借用检查从编译时挪到了运行时。即使变量不是 mut,你也可以调用 borrow_mut(),只要同一时刻没有两个可变借用同时活着:
use std::cell::RefCell;
fn main() { let balance = RefCell::new(100); // 注意:不需要 `mut`
*balance.borrow_mut() += 50; // 借用、修改,借用在 `;` 处结束 *balance.borrow_mut() -= 30;
println!("余额:{}", balance.borrow());}输出:
余额:120说句实在话:RefCell 并没有取消这条规则,只是把检查推迟了。如果你让两个 borrow_mut() 守卫同时活着,程序能编译通过,但运行时会 panic:
use std::cell::RefCell;
fn main() { let balance = RefCell::new(100);
let mut deposit = balance.borrow_mut(); let mut withdraw = balance.borrow_mut(); // 💥 `deposit` 还活着
*deposit += 50; *withdraw -= 30;}thread 'main' (12345) panicked at src/main.rs:7:32:RefCell already borrowedpanic 信息的意思是:RefCell 已经被借用了。所以,只有当数据确实是共享的(比如在 Rc<RefCell<T>> 里),而且修复 1–3 都不合适时,才去用 RefCell。它不是让编译器闭嘴的办法。
- 在循环里存放
&mut。 在for循环里写handles.push(&mut counter)会报错,提示“mutably borrowed here in the previous iteration of the loop”(在上一轮循环里已经被可变借用了)。循环每转一圈,就给同一个值多加一个&mut。修复: 存放索引或拥有所有权的值,或者直接在循环里修改这个值。 - 对
HashMap调用两次get_mut。 先let a = stock.get_mut("apples"),再let p = stock.get_mut("pears"),就借用了stock两次。修复: 在再次调用get_mut之前先用完a,或者用stock.get_disjoint_mut(["apples", "pears"])一次拿到两个。 - 两个闭包修改同一个变量。
let mut inc = || count += 1;和let mut inc2 = || count += 2;在各自存活期间都拿着一个&mut count。修复: 只用一个闭包,或者在创建第二个闭包之前最后一次调用第一个。参见闭包。
- E0502:cannot borrow as mutable because it is also borrowed as immutable:道理相同,只不过其中一个借用是只读的
&。 - E0382:borrow of moved value:一个值被移动到别处之后,你又使用了它。
- E0106:missing lifetime specifier:Rust 需要知道返回的引用能活多久。
- 所有编译错误:错误页面的完整列表。
小测验
1. Rust 什么时候会报 E0499?
2. 你先创建 let a = &mut v;,最后一次使用 a,然后创建 let b = &mut v;。会发生什么?
3. 哪个工具能让你拿到同一个 slice 中不同元素的两个 &mut 引用?
得分: 0 / 3
- E0499 — Rust error codes index(英文):这个错误的官方解释,附有一个最小示例。
- 引用与借用 — Rust 程序设计语言:介绍“同一时间只能有一个
&mut”这条规则的章节。 - Splitting Borrows — The Rustonomicon(英文):为什么结构体的字段可以分开借用,而 slice 的元素不行,以及
split_at_mut是怎么实现的。 RefCell<T>与内部可变性模式 — Rust 程序设计语言:什么时候把借用检查挪到运行时才是正确的选择。