从 HashMap 或 Vec 返回引用会导致借用持续超出其所在的范围吗? [英] Returning a reference from a HashMap or Vec causes a borrow to last beyond the scope it's in?

查看:46
本文介绍了从 HashMap 或 Vec 返回引用会导致借用持续超出其所在的范围吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个持续的编译错误,其中 Rust 抱怨我在尝试可变借用时有一个不可变借用,但不可变借用来自另一个范围,我没有带来任何东西.

I've got a persistent compile error where Rust complains that I have an immutable borrow while I'm trying to mutably borrow, but the immutable borrow is from another scope, and I'm not bringing anything across from it.

我有一些代码可以检查地图中的值,如果存在,则返回它,否则需要以各种方式对地图进行变异.问题是我似乎无法找到一种方法让 Rust 让我同时做这两个操作,即使这两个操作是完全分开的.

I have some code that checks for a value in a map, and if it's present, returns it, otherwise it needs to mutate the map in various ways. The problem is that I can't seem to find a way to get Rust let me do both, even though the two operations are totally separate.

下面是一些无意义的代码,它们遵循与我的代码相同的结构并显示出问题:

Here's some nonsensical code that follows the same structure as my code and exhibits the problem:

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 退出函数,另一个分支什么也不做,离开作用域.

This doesn't make any sense to me. How does the immutable borrow outlive that scope?! One branch of that match exits the function via return, and the other does nothing and leaves the scope.

我之前见过这种情况,我错误地将借用从某个其他变量的范围中走私出来,但这里不是这种情况!

I've seen this happen before where I was mistakenly smuggling the borrow out of the scope in some other variable, but that's not the case here!

确实,借用是通过 return 语句转义范围的,但很可笑的是,这阻止了函数中更远的借用——程序不可能返回并继续运行!如果我在那里返回其他东西,错误就会消失,所以我认为这就是借用检查器被挂断的原因.这对我来说就像一个错误.

True, the borrow is escaping the scope via the return statement, but it's ridiculous that that blocks borrowing farther down in the function -- the program cannot possibly return AND keep going! If I return something else there, the error goes away, so I think this is what the borrow checker is getting hung up on. This feels like a bug to me.

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

Unfortunately, I've been unable to find any way to rewrite this without hitting the same error, so it's a particularly nasty bug if that's the case.

推荐答案

这是一个 已知问题 将通过 非词法生命周期 的未来迭代解决,但是目前未处理,从 Rust 1.51 开始.

This is a known issue that will be solved by a future iteration of non-lexical lifetimes, but is not currently handled as of Rust 1.51.

如果您要插入到您要查找的同一键,我鼓励您使用入口 API 代替.

If you are inserting to the same key that you are looking up, I'd encourage you to use the entry API instead.

您现在可以添加一些效率低下的方法来解决此问题.如果效率低下是不可接受的,有更深层次的解决方法.

You can add a smidgen of inefficiency to work around this for now. If the inefficiency is unacceptable, there are deeper workarounds.

一般的想法是添加一个布尔值,告诉您一个值是否存在.这个布尔值不挂在引用上,所以没有借用:

The general idea is to add a boolean that tells you if a value was present or not. This boolean does not hang on to a reference, so there is no borrow:

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)
}

Vec

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

Vec

Similar cases can be solved by using the index of the element instead of the reference. Like the case above, this can introduce a bit of inefficiency due to the need to check the slice bounds again.

代替

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]
}

非词法生命周期

这些类型的示例是NLL RFC: 问题案例#3:跨函数的条件控制流.

不幸的是,这个特定案例在 Rust 1.51 中还没有准备好.如果您在 nightly 中选择使用实验性 -Zpolonius 功能,那么这些原始示例中的每一个都会按原样编译:

Unfortunately, this specific case isn't ready as of Rust 1.51. If you opt in to the experimental -Zpolonius feature in nightly, each of these original examples compile as-is:

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 1.32 中可用的 NLL 实现.

This is the same problem without returning the reference, which does work with the implementation of NLL available in Rust 1.32.

即使启用 NLL,循环中也会发生双重可变借用错误

这个问题,但在一个稍微复杂的情况下.

This problem but in a slightly more complicated case.

什么时候需要绕过 Rust 的借用检查器?

终极逃生舱口.

这篇关于从 HashMap 或 Vec 返回引用会导致借用持续超出其所在的范围吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆