尝试删除和插入 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);
}
现在,借用检查器抱怨 r
在 if
块的三行中被借用为不可变的,然后是可变的,然后又是不可变的。
我不明白发生了什么......我想因为 get
、remove
和 insert
方法有 r
作为隐式参数,它是在三个调用中借用的.但是为什么 remove
调用中的这个借用是可变的呢?
But why is it a problem that this borrow in the remove call is mutable?
问题是跨越:Rust 允许 或者 任意数量的不可变借用 或 单个可变借用,它们不能重叠。
这里的问题是 v
是对地图内容的引用,意味着 存在 v
需要借用地图直到 [=14] =] 停止使用。因此与 remove
和 insert
调用重叠,并禁止它们。
现在有多种方法可以解决这个问题。由于在这种特定情况下,您使用的是 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();
我是 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);
}
现在,借用检查器抱怨 r
在 if
块的三行中被借用为不可变的,然后是可变的,然后又是不可变的。
我不明白发生了什么......我想因为 get
、remove
和 insert
方法有 r
作为隐式参数,它是在三个调用中借用的.但是为什么 remove
调用中的这个借用是可变的呢?
But why is it a problem that this borrow in the remove call is mutable?
问题是跨越:Rust 允许 或者 任意数量的不可变借用 或 单个可变借用,它们不能重叠。
这里的问题是 v
是对地图内容的引用,意味着 存在 v
需要借用地图直到 [=14] =] 停止使用。因此与 remove
和 insert
调用重叠,并禁止它们。
现在有多种方法可以解决这个问题。由于在这种特定情况下,您使用的是 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();