小能豆

从 HashMap 或 Vec 返回引用会导致借用超出其范围?

rust

我遇到了一个持续的编译错误,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,另一个分支不执行任何操作并离开作用域。

我以前见过这种情况发生过,我错误地将借用偷偷带出其他变量的范围,但这里不是这种情况!

确实,借用是通过语句逃逸作用域的return,但荒谬的是,这会阻止函数中进一步的借用——程序不可能返回并继续运行!如果我在那里返回其他内容,错误就会消失,所以我认为这就是借用检查器所挂起的问题。这对我来说就像一个错误。

不幸的是,我一直无法找到任何方法来重写它而不遇到相同的错误,所以如果是这种情况,这将是一个特别令人讨厌的错误。


阅读 93

收藏
2024-05-21

共1个答案

小能豆

在 Rust 中,借用规则要求不可变引用(immutable reference)的生命周期必须在可变引用(mutable reference)之前结束,这是为了确保数据的安全性和避免数据竞争。在你的代码中,虽然第一个不可变借用出现在额外的作用域内,但仍然在主要作用域内返回了一个指向 map 的引用,因此 Rust 无法保证不可变引用的生命周期是否会延长到可变引用之后,导致编译错误。

解决这个问题的一种方法是使用 entry 方法来避免多次借用同一个 map。entry 方法返回一个枚举类型,该类型可以表示 map 中指定键的条目是否存在,并且可以在不同情况下进行处理。下面是一个修改后的示例:

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,从而避免了编译错误。

2024-05-21