[英]How to iterate through a Hashmap, print the key/value and remove the value in Rust?
这在任何语言中都应该是一项微不足道的任务。 这在 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
为什么它试图移动引用? 从文档中,我不认为移动/借用适用于参考。
不允许这样做的原因至少有两个:
您需要有两个对map
并发可变引用——一个由for
循环中使用的迭代器保存,另一个保存在变量map
以调用map.remove
。
你有钥匙,并试图变异地图时地图内的值引用。 如果您被允许以任何方式修改映射,这些引用可能会失效,从而为内存不安全打开大门。
Rust 的一个核心原则是Aliasing XOR 可变性。 您可以有多个对一个值的不可变引用,也可以有一个对它的可变引用。
我不认为移动/借用适用于参考。
每种类型都遵循 Rust 的移动规则和可变别名。 请让我们知道文档的哪一部分不是这样我们可以解决这个问题。
为什么它试图移动引用?
这由两部分组合而成:
当您for (k, v) in map {}
调用for (k, v) in map {}
的所有权map
转移到 for 循环,现在已经消失了。
我会执行地图( &*map
)的不可变借用并对其进行迭代。 最后,我会清除整个事情:
fn do_it(map: &mut HashMap<String, String>) {
for (key, value) in &*map {
println!("{} / {}", key, value);
}
map.clear();
}
使用以字母“A”开头的键删除每个值
我会使用HashMap::retain
:
fn do_it(map: &mut HashMap<String, String>) {
map.retain(|key, value| {
println!("{} / {}", key, value);
!key.starts_with("a")
})
}
这保证了在实际修改映射时key
和value
不再存在,因此它们本应拥有的任何借用现在都消失了。
这在任何语言中都应该是一项微不足道的任务。
Rust 会阻止您在迭代地图时对其进行变异。 在大多数语言中,这是允许的,但通常行为没有明确定义,删除项目会干扰迭代,影响其正确性。
为什么它试图移动引用?
HashMap
实现IntoIterator
, 所以你的循环相当于:
for (key, value) in map.into_iter() {
println!("{} / {}", key, value);
map.remove(key);
}
如果您查看into_iter
的定义,您会发现它需要self
,而不是&self
或&mut self
。 您的变量map
是一个可变参考,并IntoIterator
是为实现&mut HashMap
-在self
中into_iter
是&mut HashMap
,而不是HashMap
。 无法复制可变引用(因为一次只能存在一个对任何数据的可变引用),因此该可变引用被移动。
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
,这是更好的。 它更简洁,不会像我所做的那样进行不必要的复制。
Rust 实际上支持针对这个问题的大量潜在解决方案,尽管我自己也发现这种情况起初有点令人困惑,而且每次我需要对我的哈希图进行更复杂的处理时。
.drain()
。 .drain()
具有获取/拥有而不是借用值的优势。.drain_filter()
。.drain_filter()
的闭包参数中改变它们,但这将在更改后检查是否删除。.drain_filter()
它们。.clear()
来删除所有元素,在你完成遍历它们以打印它们之后。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.