首页 文章

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

提问于
浏览
15

我有一个持久的编译错误,其中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 语句来逃避范围,但是这种方法阻止在函数中进一步向下借用是荒谬的 - 程序不可能返回并继续前进!如果我在那里返回其他东西,那么错误就会消失,所以我认为这是借用检查器被挂起的东西 . 这对我来说就像一个错误 .

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

1 回答

  • 10

    这是一个由non-lexical lifetimes解决的known issue,它本身是以MIR为基础的 . 如果碰巧你正在插入你正在查找的同一个键,我建议你改为use the entry API .

    你可以添加一个效率低下的smidgen来解决这个问题 .

    HashMap

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

    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

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

    代替

    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 RFCProblem case #3: conditional control flow across functions中的主要案例之一 .

    不幸的是,从Rust 1.32开始,这个具体案例还没有准备好 . 如果您在夜间选择使用实验 -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 1.32中可用的NLL的实现一起工作 .

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

    终极逃生舱 .

相关问题