如何遍历Hashmap,打印键/值并删除Rust中的值?

ada*_*dev 18 iteration hashmap mutability 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() {}
Run Code Online (Sandbox Code Playgroud)

这是编译器错误:

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
Run Code Online (Sandbox Code Playgroud)

为什么要试图移动参考?从文档中,我不认为移动/借用适用于引用.

She*_*ter 16

至少有两个原因导致不允许这样做:

  1. 你需要有两个并发的可变引用map- 一个由for循环中使用的迭代器保持,一个在变量map中调用map.remove.

  2. 你必须键和值参考范围内试图突变地图时的地图.如果允许您以任何方式修改地图,这些引用可能会失效,从而为内存不安全打开了大门.

核心Rust原则是别名XOR Mutability.您可以对值具有多个不可变引用,或者您可以对其进行单个可变引用.

我不认为移动/借用适用于参考文献.

每种类型都受Rust的移动规则以及可变别名的限制.请让我们知道文档的哪些部分说不是,所以我们可以解决这个问题.

为什么要试图移动参考?

这由两部分组成:

  1. 您只能有一个可变引用
  2. for循环取值按值迭代

当你打电话时for (k, v) in map {},所有权map转移到for循环,现在已经消失.


我将执行map(&*map)的不可变借用并迭代它.最后,我清楚整个事情:

fn do_it(map: &mut HashMap<String, String>) {
    for (key, value) in &*map {
        println!("{} / {}", key, value);
    }
    map.clear();
}
Run Code Online (Sandbox Code Playgroud)

使用以字母"A"开头的键删除每个值

我用的是HashMap::retain:

fn do_it(map: &mut HashMap<String, String>) {
    map.retain(|key, value| {
        println!("{} / {}", key, value);

        !key.starts_with("a")
    })
}
Run Code Online (Sandbox Code Playgroud)

当地图实际被修改时,这保证了key并且value不再存在,因此任何借用它们现在都已经消失了.

  • 这导致我遇到了一个更奇怪的问题,但我怀疑方法调用语法掩盖了问题。https://play.rust-lang.org/?gist=ecf6d9bdbe8e1ad99e5fb3c35c402d1c&amp;version=stable (2认同)
  • @Shepmaster所以现在我知道为什么 https://doc.rust-lang.org/std/iter/index.html#for-loops-and-intoiterator 写 `IntoIterator::into_iter(values)` 而不是 `values.into_iter ()`。 (2认同)

小智 8

Rust 实际上支持这个问题的多种潜在解决方案,尽管我自己一开始也发现这种情况有点令人困惑,而且每次我需要对哈希图进行更复杂的处理时,都会再次出现这种情况。

  • 要在删除项目时迭代它们,请使用.drain(). .drain()具有获取/拥有而不是借用价值的优势。
  • 如果您只想有条件地删除其中一些,请使用.drain_filter().
  • 如果您需要改变每个项目但只想删除其中一些,您可以在.drain_filter()的闭包参数中改变它们,但这将在更改后检查是否删除。
  • 如果需要在更改之前检查是否已删除,请使用变量来存储检查结果,然后在最后返回该变量。一种稍微慢一些但可能更清晰的替代方案是在一个 for 循环中改变它们,然后.drain_filter()在另一个 for 循环或映射中改变它们。
  • 您也可以简单地允许散列图在函数末尾删除,而不是在函数参数中借用它,并在需要时初始化一个新的散列图。显然,这完全删除了哈希图。显然,您可能希望保留哈希图,这样就不会一遍又一遍地重新初始化它。
  • .clear()在完成对所有元素的迭代以打印它们之后,您还可以调用删除所有元素。


Pet*_*all 7

这在任何语言中都应该是一项微不足道的任务.

你在迭代它时,Rust会阻止你改变地图.在大多数语言中,这是允许的,但通常行为没有明确定义,删除项目可能会干扰迭代,从而影响其正确性.

为什么要试图移动参考?

HashMap实现IntoIterator,所以你的循环相当于:

for (key, value) in map.into_iter() {
    println!("{} / {}", key, value);
    map.remove(key);
}
Run Code Online (Sandbox Code Playgroud)

如果你看一下定义into_iter,你会发现它需要self,而不是&self&mut self.您的变量map是一个引用,因此它被隐式取消引用以获取self,这就是错误说*map已被移动的原因.

有意构建API,以便在循环结构时不会做任何危险.循环完成后,结构的所有权将被放弃,您可以再次使用它.

一种解决方案是跟踪要在a中删除的项目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);
    }
}
Run Code Online (Sandbox Code Playgroud)

您还可以使用迭代器将地图过滤为新地图.也许是这样的:

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

但我刚看到Shepmaster的编辑 - 我忘记了retain,哪个更好.它更简洁,不会像我一样做不必要的复制.

  • “通常行为没有明确定义,删除项目会干扰迭代,影响其正确性。” 说得好。这类东西引起的bug非常奇怪,之前用C++花了几十个小时追查。特别是在嵌套循环中。谢谢。 (5认同)