繁体   English   中英

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

[英]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

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

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

  1. 您需要有两个对map并发可变引用——一个由for循环中使用的迭代器保存,另一个保存在变量map以调用map.remove

  2. 你有钥匙,并试图变异地图时地图的值引用。 如果您被允许以任何方式修改映射,这些引用可能会失效,从而为内存不安全打开大门。

Rust 的一个核心原则是Aliasing XOR 可变性 您可以有多个对一个值的不可变引用,也可以有一个对它的可变引用。

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

每种类型都遵循 Rust 的移动规则和可变别名。 请让我们知道文档的哪一部分不是这样我们可以解决这个问题。

为什么它试图移动引用?

这由两部分组合而成:

  1. 你只能有一个可变引用,所以可变引用不实现Copy trait
  2. for循环取值按值迭代

当您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")
    })
}

这保证了在实际修改映射时keyvalue不再存在,因此它们本应拥有的任何借用现在都消失了。

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

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 -在selfinto_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()的闭包参数中改变它们,但这将在更改检查是否删除。
  • 如果您需要在更改之前检查删除,请使用一个变量来存储检查结果,然后在最后返回该变量。 一个稍微慢一点但可能更清楚的替代方法是在一个 for 循环中改变它们,然后在另一个 for 循环或映射中.drain_filter()它们。
  • 您也可以通过不在函数参数中借用它来简单地允许哈希图在函数末尾删除,并在需要时初始化一个新的。 显然,这完全删除了哈希图。 显然,您可能希望保留散列图,这样您就不会一遍又一遍地重新初始化它。
  • 你也可以调用.clear()来删除所有元素,在你完成遍历它们以打印它们之后。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM