尝试删除和插入 HashMap 时发生错误的可变借用 (E0502)

Erronous mutable borrow (E0502) when trying to remove and insert into a HashMap

我是 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 调用中的这个借用是可变的呢?

But why is it a problem that this borrow in the remove call is mutable?

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

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

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

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

虽然它的灵活性有限,因为它仅适用于 Copy 类型[0]。

在这种特定情况下,它可能并不重要,因为 Copy 很便宜,但为了安全起见,使用 Rust 提供的高级 APIs 仍然更有意义,为了清楚起见,并且因为您最终会需要它们来处理不那么琐碎的类型。

最简单的是只使用 get_mut: where get returns an Option<&V>, get_mut returns 和 Option<&mut V>,这意味着您可以...就地更新值,您不需要将其取出,并且您不需要将其重新插入(您也不需要单独查找,但您已经不需要它了):

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

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

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

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

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

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

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

所以即使不使用高级APIs,原代码也可以简化为:

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

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

也不同于大多数 Rust 语言的 HashMap::insert returns 旧值 (f any),这里不是问题,但在某些情况下可能有用。

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

问题是您在获取 v 时保留了不可变引用。由于是u64,只是隐式clone所以不再涉及参考:

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

Playground