我遇到了一个持续的编译错误,Rust 抱怨我在尝试可变借用时有一个不可变借用,但不可变借用来自另一个作用域,并且我没有从中带来任何东西。
我有一些代码检查映射中的值,如果存在,则返回它,否则需要以各种方式改变映射。问题是我似乎找不到一种方法让 Rust 让我同时执行这两项操作,即使这两个操作是完全独立的。
这是一些无意义的代码,它们遵循与我的代码相同的结构并显示了问题:
use std::collections::BTreeMap; fn do_stuff(map: &mut BTreeMap<i32, i32>, key: i32) -> Option<&i32> { // extra scope in vain attempt to contain the borrow { // borrow immutably if let Some(key) = map.get(&key) { return Some(key); } } // now I'm DONE with the immutable borrow, but rustc still thinks it's borrowed map.insert(0, 0); // borrow mutably, which errors None }
这个错误是:
error[E0502]: cannot borrow `*map` as mutable because it is also borrowed as immutable --> src/lib.rs:14:5 | 3 | fn do_stuff(map: &mut BTreeMap<i32, i32>, key: i32) -> Option<&i32> { | - let's call the lifetime of this reference `'1` ... 7 | if let Some(key) = map.get(&key) { | --- immutable borrow occurs here 8 | return Some(key); | --------- returning this value requires that `*map` is borrowed for `'1` ... 14 | map.insert(0, 0); // borrow mutably, which errors | ^^^^^^^^^^^^^^^^ mutable borrow occurs here
这对我来说没有任何意义。不可变借用如何超越该范围?其中一个分支match通过 退出函数return,另一个分支不执行任何操作并离开作用域。
match
return
我以前见过这种情况发生过,我错误地将借用偷偷带出其他变量的范围,但这里不是这种情况!
确实,借用是通过语句逃逸作用域的return,但荒谬的是,这会阻止函数中进一步的借用——程序不可能返回并继续运行!如果我在那里返回其他内容,错误就会消失,所以我认为这就是借用检查器所挂起的问题。这对我来说就像一个错误。
不幸的是,我一直无法找到任何方法来重写它而不遇到相同的错误,所以如果是这种情况,这将是一个特别令人讨厌的错误。
在 Rust 中,借用规则要求不可变引用(immutable reference)的生命周期必须在可变引用(mutable reference)之前结束,这是为了确保数据的安全性和避免数据竞争。在你的代码中,虽然第一个不可变借用出现在额外的作用域内,但仍然在主要作用域内返回了一个指向 map 的引用,因此 Rust 无法保证不可变引用的生命周期是否会延长到可变引用之后,导致编译错误。
解决这个问题的一种方法是使用 entry 方法来避免多次借用同一个 map。entry 方法返回一个枚举类型,该类型可以表示 map 中指定键的条目是否存在,并且可以在不同情况下进行处理。下面是一个修改后的示例:
entry
use std::collections::BTreeMap; fn do_stuff(map: &mut BTreeMap<i32, i32>, key: i32) -> Option<&i32> { // 使用 entry 方法来检查键是否存在 match map.entry(key) { // 如果键存在,则返回对应的值的引用 std::collections::btree_map::Entry::Occupied(entry) => Some(entry.into_mut()), // 如果键不存在,则插入新的键值对,并返回新值的引用 std::collections::btree_map::Entry::Vacant(entry) => { entry.insert(0); None } } } fn main() { let mut map = BTreeMap::new(); map.insert(1, 10); map.insert(2, 20); if let Some(value) = do_stuff(&mut map, 2) { println!("Value: {}", value); } println!("{:?}", map); }
在这个修改后的版本中,我们使用 entry 方法来检查键是否存在于 map 中。如果键存在,我们返回一个可变引用,允许对其进行修改。如果键不存在,我们插入一个新的键值对,并返回一个空的引用。这样,我们就可以避免在同一作用域内多次借用 map,从而避免了编译错误。