【问题标题】:Erronous mutable borrow (E0502) when trying to remove and insert into a HashMap尝试删除并插入 HashMap 时出现错误的可变借用 (E0502)
【发布时间】:2021-06-25 10:23:10
【问题描述】:

我是 Rust 的初学者并尝试使用 HashMap<u64, u64>。我想删除一个元素并使用修改后的值插入它:

let mut r = HashMap::new();
let mut i = 2;
...
if r.contains_key(&i) {
    let v = r.get(&i).unwrap();
    r.remove(&i);
    r.insert(i, v+1);
}

现在,借用检查器抱怨rif 块的三行中被借用为不可变,然后是可变的,然后又是不可变的。 我不明白发生了什么......我想因为getremoveinsert 方法有r 作为隐含参数,它是在三个调用中借用的。但是为什么remove调用中的这个借用是可变的呢?

【问题讨论】:

    标签: rust hashmap borrow-checker ownership


    【解决方案1】:

    但是为什么 remove 调用中的这个借用是可变的呢?

    问题在于跨越:Rust 允许或者任意数量的不可变借用单个可变借用,它们不能重叠。

    这里的问题是v 是对地图内容的引用,这意味着v存在 需要借用地图直到v 停止使用。因此,这与 removeinsert 调用重叠,并禁止它们。

    现在有多种方法可以解决此问题。由于在这种特定情况下您使用的是u64,即Copy,因此您可以取消引用,它会复制您从地图中获得的值,无需借用:

    if r.contains_key(&i) {
        let v = *r.get(&i).unwrap();
        r.remove(&i);
        r.insert(i, v+1);
    }
    

    但它的灵活性有限,因为它仅适用于Copy types[0]。

    在这种特定情况下,它可能并不重要,因为Copy 很便宜,但使用 Rust 提供的高级 API 仍然更有意义,为了安全,为了清晰,因为你最终会需要它们用于不太琐碎的类型。

    最简单的方法是只使用get_mut:其中get 返回一个Option<&V>get_mut 返回一个Option<&mut V>,这意味着您可以...就地更新值,您不需要把它取出来,你不需要重新插入(你也不需要单独的查找,但你已经不需要那个了):

    if let Some(v) = r.get_mut(&i) {
        *v += 1;
    }
    

    对于您的用例来说绰绰有余。

    第二个选项是the Entry API,它会永远毁掉所有其他的 hashmap API。我不是在开玩笑,所有其他语言都会变得非常令人沮丧,您可能希望避免点击该链接(尽管您最终还是需要了解它,因为它解决了真正的借用和效率问题)。

    这里并没有真正显示它的内容,因为您的用例很简单,get_mut 比工作多,但无论如何,您可以将增量写为:

    r.entry(i).and_modify(|v| *v+=1);
    

    顺便说一句,在大多数语言中(当然在 Rust 中也是如此),当你在 hashmap 中插入一个项目时,如果有一个项目,旧值就会被驱逐。所以remove 调用已经是多余的,完全没有必要。

    并且模式匹配 Option(例如 HashMap::get 返回的)通常比煞费苦心地按程序执行所有低级位更安全、更简洁、更快。

    因此即使不使用高级 API,原始代码也可以简化为:

    if let Some(&v) = r.get(&i) {
        r.insert(i, v+1);
    }
    

    我仍然推荐 get_mut 版本,因为它更简单,避免双重查找,并且适用于非 Copy 类型,但 YMMV。

    也与大多数语言不同,Rust 的 HashMap::insert 返回旧值(如果有的话),这不是问题,但在某些情况下可能很有用。

    [0] 以及 Clone 的,通过显式调用 .clone(),可能会或可能不会转化为显着的性能影响,具体取决于您要克隆的类型。

    【讨论】:

      【解决方案2】:

      问题是您在获取v 时保留了一个不可变引用。既然是u64,就隐含clone所以就没有更多的引用了:

      let v = r.get(&i).unwrap().clone();
      

      Playground

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-12-02
        • 2015-12-08
        • 1970-01-01
        • 1970-01-01
        • 2014-11-21
        • 1970-01-01
        相关资源
        最近更新 更多