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

时间:2017-08-16 23:38:19

标签: hashmap rust iteration mutability

这应该是任何语言的一项微不足道的任务。这不适用于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

为什么要尝试移动参考?从文档中,我并不认为移动/借用适用于参考文献。

3 个答案:

答案 0 :(得分:14)

至少有两个理由不允许这样做:

  1. 您需要对map提供两个并发可变引用 - 一个由for循环中使用的迭代器保存,另一个由变量map保存,以调用{{} 1}}。

  2. 在尝试改变地图时,您在地图中引用了键和 值。如果允许您以任何方式修改地图,这些引用可能会失效,从而为内存不安全打开了大门。

  3. 核心Rust原则是别名XOR可变性。您可以对值赋予多个不可变引用,也可以对其进行单个可变引用。

      

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

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

      

    为什么要尝试移动参考?

    这由两部分组成:

    1. 您只能拥有一个可变参考
    2. map.remove循环take the value to iterate over by value
    3. 当您致电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实施IntoIteratorso 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() 的闭包参数中改变它们,但这将在更改之后检查删除 .
  • 如果您需要在更改之前检查删除,请使用一个变量来存储检查结果,然后在最后返回该变量。一种稍微慢一点但可能更明确的替代方法是在一个 for 循环中改变它们,然后在另一个 for 循环或映射中 .drain_filter() 它们。
  • 您也可以通过不在函数参数中借用它来简单地允许哈希图在函数末尾删除,并在需要时初始化一个新的。显然,这完全删除了哈希图。显然,您可能希望保留哈希图,以免一遍又一遍地重新初始化它。
  • 您还可以调用 .clear() 来删除所有元素,在您完成遍历它们以打印它们之后。