在尝试实现查找或插入时,HashMap借用了问题

时间:2014-06-18 13:53:16

标签: rust

我尝试实现自己的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时,我们将其借用到匹配语句的范围内,因此我们无法在此处调用self.types.insert。我试图将分支中的方法移出匹配语句,但它也失败了。

我找到的唯一可行解决方案需要两次调用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()
}

我如何处理这种情况?

2 个答案:

答案 0 :(得分:8)

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

答案 1 :(得分:1)

请注意,在第一种情况下,当密钥存在于地图中时,您正在执行一次查找,而当不存在时,三次。在任何一种情况下,您的上次尝试都会进行两次查找。这是后者的一些美化版本:

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都优于上述(例如,它可能更有效,实际上需要更少的查找),所以我建议坚持使用它。