这应该是任何语言的一项微不足道的任务。这不适用于Rust。
use std::collections::HashMap;
fn do_it(map: &mut HashMap<String, String>) {
for (key, value) in map {
println!("{} / {}", key, value);
map.remove(key);
}
}
fn main() {}
这是编译器错误:
error[E0382]: use of moved value: `*map`
--> src/main.rs:6:9
|
4 | for (key, value) in map {
| --- value moved here
5 | println!("{} / {}", key, value);
6 | map.remove(key);
| ^^^ value used here after move
|
= note: move occurs because `map` has type `&mut std::collections::HashMap<std::string::String, std::string::String>`, which does not implement the `Copy` trait
为什么要尝试移动参考?从文档中,我并不认为移动/借用适用于参考文献。
答案 0 :(得分:14)
至少有两个理由不允许这样做:
您需要对map
提供两个并发可变引用 - 一个由for
循环中使用的迭代器保存,另一个由变量map
保存,以调用{{} 1}}。
在尝试改变地图时,您在地图中引用了键和 值。如果允许您以任何方式修改地图,这些引用可能会失效,从而为内存不安全打开了大门。
核心Rust原则是别名XOR可变性。您可以对值赋予多个不可变引用,也可以对其进行单个可变引用。
我认为移动/借用不适用于参考文献。
每种类型都受Rust的移动规则以及可变别名的限制。请让我们知道文档的哪些部分说不是,所以我们可以解决这个问题。
为什么要尝试移动参考?
这由两部分组成:
map.remove
循环take the value to iterate over by value 当您致电for
时,for (k, v) in map {}
的所有权将转移到for循环,现在已经消失。
我会对地图(map
)执行不可变借用并迭代它。最后,我清楚了解整个事情:
&*map
使用以字母“A”
开头的键删除每个值
我使用HashMap::retain
:
fn do_it(map: &mut HashMap<String, String>) {
for (key, value) in &*map {
println!("{} / {}", key, value);
}
map.clear();
}
这可以保证在实际修改地图时fn do_it(map: &mut HashMap<String, String>) {
map.retain(|key, value| {
println!("{} / {}", key, value);
!key.starts_with("a")
})
}
和key
不再存在,因此任何借用它们现在都已消失。
答案 1 :(得分:6)
这应该是任何语言的一项微不足道的任务。
Rust正在阻止你在迭代地图时改变地图。在大多数语言中,这是允许的,但通常行为没有明确定义,删除项目可能会干扰迭代,从而影响其正确性。
为什么要尝试移动参考?
HashMap
实施IntoIterator
,so your loop is equivalent to:
for (key, value) in map.into_iter() {
println!("{} / {}", key, value);
map.remove(key);
}
如果查看definition of into_iter
,您会发现它需要self
,而不是&self
或&mut self
。您的变量map
是一个引用,因此隐式取消引用以获取self
,这就是错误表明已移动*map
的原因。
有意识地构建API,以便在循环结构时不会做任何危险。循环完成后,结构的所有权将被放弃,您可以再次使用它。
一种解决方案是跟踪您要在Vec
中删除的项目,然后将其删除:
fn do_it(map: &mut HashMap<String, String>) {
let mut to_remove = Vec::new();
for (key, value) in &*map {
if key.starts_with("A") {
to_remove.push(key.to_owned());
}
}
for key in to_remove.iter() {
map.remove(key);
}
}
您也可以使用迭代器将地图过滤为新地图。也许是这样的:
fn do_it(map: &mut HashMap<String, String>) {
*map = map.into_iter().filter_map(|(key, value)| {
if key.starts_with("A") {
None
} else {
Some((key.to_owned(), value.to_owned()))
}
}).collect();
}
但我刚看到Shepmaster的编辑 - 我忘记了retain
,这更好。它更简洁,不会像我一样做不必要的复制。
答案 2 :(得分:0)
rust 实际上支持针对这个问题的大量潜在解决方案,尽管我自己也发现这种情况一开始有点令人困惑,每次我需要对我的哈希图进行更复杂的处理时。
.drain()
。 .drain()
具有获取/拥有而不是借用价值的优势。.drain_filter()
。.drain_filter()
的闭包参数中改变它们,但这将在更改之后检查删除 ..drain_filter()
它们。.clear()
来删除所有元素,在您完成遍历它们以打印它们之后。