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

标签 rust borrow-checker

我有一个持续的编译错误,在我试图可变借用时,Rust 提示我有一个不可变借用,但不可变借用来自另一个范围,我没有从它带来任何东西。

我有一些代码检查 map 中的值,如果它存在,则返回它,否则它需要以各种方式改变 map 。问题是我似乎无法找到一种方法让 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 语句逃离作用域,但荒谬的是,这会阻止在函数中进一步向下借用——程序不可能返回并继续运行!如果我在那里归还其他东西,错误就会消失,所以我认为这就是借用检查器挂断的原因。这对我来说就像一个错误。

不幸的是,我一直无法找到任何方法来重写它而不会遇到同样的错误,所以如果是这样的话,这是一个特别讨厌的错误。

最佳答案

这是一个 known issue这将通过 non-lexical lifetimes 的 future 迭代来解决,但从 Rust 1.57 开始当前未处理

如果您要插入到您正在查找的同一个键,我建议您使用 use the entry API相反。

您现在可以添加一点低效率来解决这个问题。如果效率低下是 Not Acceptable ,there are deeper workarounds .

HashMap

一般的想法是添加一个 bool 值,告诉您值是否存在。这个 bool 值不依赖于一个引用,所以没有借用:

use std::collections::BTreeMap;

fn do_stuff(map: &mut BTreeMap<i32, i32>, key: i32) -> Option<&i32> {
    if map.contains_key(&key) {
        return map.get(&key);
    }

    map.insert(0, 0);
    None
}

fn main() {
    let mut map = BTreeMap::new();
    do_stuff(&mut map, 42);
    println!("{:?}", map)
}

向量

类似的情况可以通过使用元素的索引而不是引用来解决。与上述情况一样,由于需要再次检查切片边界,这可能会导致效率低下。

代替

fn find_or_create_five<'a>(container: &'a mut Vec<u8>) -> &'a mut u8 {
    match container.iter_mut().find(|e| **e == 5) {
        Some(element) => element,
        None => {
            container.push(5);
            container.last_mut().unwrap()
        }
    }
}

你可以这样写:

fn find_or_create_five<'a>(container: &'a mut Vec<u8>) -> &'a mut u8 {
    let idx = container.iter().position(|&e| e == 5).unwrap_or_else(|| {
        container.push(5);
        container.len() - 1    
    });
    &mut container[idx]
}

非词法生命周期

这些类型的示例是 the NLL RFC 中的主要案例之一: Problem case #3: conditional control flow across functions .

不幸的是,从 Rust 1.57 开始,这个特定案例还没有准备好。如果您在 nightly (RUSTFLAGS="-Z polonius"cargo +nightly check) 中选择加入实验性 -Zpolonius 功能,则这些原始示例中的每一个都按原样编译:

use std::collections::BTreeMap;

fn do_stuff(map: &mut BTreeMap<i32, i32>, key: i32) -> Option<&i32> {
    if let Some(key) = map.get(&key) {
        return Some(key);
    }

    map.insert(0, 0);
    None
}
fn find_or_create_five(container: &mut Vec<u8>) -> &mut u8 {
    match container.iter_mut().find(|e| **e == 5) {
        Some(element) => element,
        None => {
            container.push(5);
            container.last_mut().unwrap()
        }
    }
}

另见:

关于rust - 从 HashMap 或 Vec 返回引用会导致借用超出其所在范围?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55447297/

相关文章:

rust - 如何划分可变借用的向量?

rust - 提取字符串内的宏参数

rust - 为什么不能在同一结构中存储值和对该值的引用?

rust - 如何将引用返回到 Rc<RefCell<>> 函数参数?

Rust:在无限循环中借用检查器

rust - 包含文件后,柴油和火箭的进口就会中断

rust - 在 libp2p 中实现对等点发现

rust - 在 Rust 中缓存资源的惯用方法

rust - 如何解决 [E0382] : use of moved value in a for loop?

Rust 借用/所有权