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

Jak*_* W. 1 hashmap ownership rust borrow-checker

我是 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);
}
Run Code Online (Sandbox Code Playgroud)

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

Mas*_*inn 5

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

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

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

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

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

但是,这在灵活性方面受到限制,因为它仅适用于 Copy类型 [0]。

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

最简单的方法是使用get_mut: whereget返回 an Option<&V>get_mut返回 an Option<&mut V>,这意味着您可以...就地更新值,您不需要将其取出,也不需要将其插入(或您是否需要单独查找,但您实际上已经不需要了):

if let Some(v) = r.get_mut(&i) {
    *v += 1;
}
Run Code Online (Sandbox Code Playgroud)

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

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

它并没有真正在这里展示它的东西,因为你的用例很简单,get_mut而且不仅仅是工作,但无论如何,你可以将增量写为:

r.entry(i).and_modify(|v| *v+=1);
Run Code Online (Sandbox Code Playgroud)

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

并且模式匹配一​​个Option(例如返回的HashMap::get)通常比煞费苦心地和程序化地完成所有低级位更安全、更干净、更快。

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

if let Some(&v) = r.get(&i) {
    r.insert(i, v+1);
}
Run Code Online (Sandbox Code Playgroud)

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

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

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