但是为什么 remove 调用中的这个借用是可变的呢?
问题在于跨越:Rust 允许或者任意数量的不可变借用或单个可变借用,它们不能重叠。
这里的问题是v 是对地图内容的引用,这意味着v 的存在 需要借用地图直到v 停止使用。因此,这与 remove 和 insert 调用重叠,并禁止它们。
现在有多种方法可以解决此问题。由于在这种特定情况下您使用的是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(),可能会或可能不会转化为显着的性能影响,具体取决于您要克隆的类型。