【问题标题】:HashMap borrow issue when trying to implement find or insert尝试实现查找或插入时的 HashMap 借用问题
【发布时间】:2014-06-18 13:53:16
【问题描述】:

我尝试实现类似于 find_or_insert 的方法,如下所示:

use std::collections::HashMap;

pub struct SomeManager {
    next: i32,
    types: HashMap<i32, i32>,
}

impl SomeManager {
    pub fn get_type<'a>(&'a mut self, k: i32) -> &'a i32 {
        match self.types.get(&k) {
            Some(ref x) => return *x,
            None => {
                self.types.insert(k, self.next);
                self.next += 1;
                return self.types.get(&k).unwrap();
            }
        }
    }
}

fn main() {}

错误:

error[E0502]: cannot borrow `self.types` as mutable because it is also borrowed as immutable
  --> src/main.rs:13:17
   |
10 |         match self.types.get(&k) {
   |               ---------- immutable borrow occurs here
...
13 |                 self.types.insert(k, self.next);
   |                 ^^^^^^^^^^ mutable borrow occurs here
...
18 |     }
   |     - immutable borrow ends here

我知道有一些标准方法可以实现此功能,但我希望此方法尽可能轻量级 - 它会被非常频繁地调用,并且几乎所有时间值都已经存在。

据我了解,当我们调用 self.types.get 时,我们将其借用到 ma​​tch 语句的范围内,因此我们不能在这里调用 self.types.insert。我试图将 None 分支中的方法移出 ma​​tch 语句,但它也失败了。

我发现的唯一可行的解​​决方案需要调用 get 两次:

pub fn get_type<'a>(&'a mut self, k: i32) -> &'a i32 {
    let is_none = match self.types.get(&k) {
        Some(ref x) => false,
        None => true,
    };
    if is_none {
        self.types.insert(k, self.next);
        self.next += 1;
    }
    self.types.get(&k).unwrap()
}

我该如何解决这种情况?

【问题讨论】:

    标签: rust


    【解决方案1】:

    HashMap 上有一些方法可以实现这些复杂的情况。最值得注意的是,对于您的情况,HashMap::entryEntry::or_insert_with

    pub fn get_type<'a>(&'a mut self, k: i32) -> &'a i32 {
        self.types.entry(k).or_insert_with(|| {
            let value = self.next;
            self.next += 1;
            value
        })
    }
    

    但是,在你的情况下,里面有 self 的借用,所以这行不通。

    因此,我们将self.next 的借用转移到闭包之外,以便编译器可以推断它与self.types 不相交。只需一次查找即可解决问题,这是应该的。

    pub fn get_type<'a>(&'a mut self, k: i32) -> &'a i32 {
        let next = &mut self.next;
    
        self.types.entry(k).or_insert_with(|| {
            let value = *next;
            *next += 1;
            value
        })
    }
    

    【讨论】:

    • @VladimirMatveev:观察!
    • 我不知道将&amp;mut self.path 之类的东西传递给以self 作为接收器的方法是可能的。真的很棒。
    • @VladimirMatveev:它采用self,至少与selfself.path 不同;它占用了self.types,使得借用不相交。
    • 对于那些仍然遇到这个问题的人来说,这些方法似乎不再存在,并且执行此操作的新方法是使用 .entry() API,如下所述:stackoverflow.com/a/28512504/3000133
    【解决方案2】:

    请注意,在您的第一种情况下,当键存在于地图中时,您正在执行 one 查找,而当它不存在时,您正在执行 3 查找。无论哪种情况,您的最后一次尝试都会进行 两次 查找。这是后者的美化版本:

    pub fn get_type<'a>(&'a mut self, k: i32) -> &'a i32 {
        let contains = self.types.contains_key(&k);
        if !contains {
            self.types.insert(k, self.next);
            self.next += 1;
        }
        self.types.get(&k).unwrap()
    }
    

    由于借用限制,我认为如果没有地图实现的一些支持,就不可能避免第二次查找。

    无论如何,使用solution by Chris Morgan 比上面的要好(例如,它可能更高效,实际上需要更少的查找),所以我建议坚持下去。

    【讨论】:

    • 第二种解决方案对我来说是理想的,但它也无法编译 - 出于某种原因,它表示先前的借用(匹配)在函数的末尾结束
    • @Mikhail,看看这个。
    • 返回一个引用意味着v 的生命周期是'a,因此self.types 的借用是生命周期'a——你放在它周围的花括号将无济于事全部。
    猜你喜欢
    • 2021-06-25
    • 1970-01-01
    • 2020-11-26
    • 2021-10-25
    • 2023-01-30
    • 1970-01-01
    • 1970-01-01
    • 2017-11-05
    • 1970-01-01
    相关资源
    最近更新 更多