简体   繁体   English

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

[英]How to iterate through a Hashmap, print the key/value and remove the value in Rust?

This should be a trivial task in any language.这在任何语言中都应该是一项微不足道的任务。 This isn't working 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() {}

Here's the compiler error:这是编译器错误:

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

Why it is trying to move a reference?为什么它试图移动引用? From the documentation, I didn't think moving/borrowing applied to references.从文档中,我不认为移动/借用适用于参考。

There are at least two reasons why this is disallowed:不允许这样做的原因至少有两个:

  1. You would need to have two concurrent mutable references to map — one held by the iterator used in the for loop and one in the variable map to call map.remove .您需要有两个对map并发可变引用——一个由for循环中使用的迭代器保存,另一个保存在变量map以调用map.remove

  2. You have references to the key and the value within the map when trying to mutate the map.你有钥匙,并试图变异地图时地图的值引用。 If you were allowed to modify the map in any way, these references could be invalidated, opening the door for memory unsafety.如果您被允许以任何方式修改映射,这些引用可能会失效,从而为内存不安全打开大门。

A core Rust principle is Aliasing XOR Mutability . Rust 的一个核心原则是Aliasing XOR 可变性 You can have multiple immutable references to a value or you can have a single mutable reference to it.您可以有多个对一个值的不可变引用,也可以有一个对它的可变引用。

I didn't think moving/borrowing applied to references.我不认为移动/借用适用于参考。

Every type is subject to Rust's rules of moving as well as mutable aliasing.每种类型都遵循 Rust 的移动规则和可变别名。 Please let us know what part of the documentation says it isn't so we can address that.请让我们知道文档的哪一部分不是这样我们可以解决这个问题。

Why it is trying to move a reference?为什么它试图移动引用?

This is combined of two parts:这由两部分组合而成:

  1. You can only have a single mutable reference, so mutable references don't implement the Copy trait你只能有一个可变引用,所以可变引用不实现Copy trait
  2. for loops take the value to iterate over by value for循环取值按值迭代

When you call for (k, v) in map {} , the ownership of map is transferred to the for loop and is now gone.当您for (k, v) in map {}调用for (k, v) in map {}的所有权map转移到 for 循环,现在已经消失了。


I'd perform an immutable borrow of the map ( &*map ) and iterate over that.我会执行地图( &*map )的不可变借用并对其进行迭代。 At the end, I'd clear the whole thing:最后,我会清除整个事情:

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

remove every value with a key that starts with the letter "A"使用以字母“A”开头的键删除每个值

I'd use HashMap::retain :我会使用HashMap::retain

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

        !key.starts_with("a")
    })
}

This guarantees that key and value no longer exist when the map is actually modified, thus any borrow that they would have had is now gone.这保证了在实际修改映射时keyvalue不再存在,因此它们本应拥有的任何借用现在都消失了。

This should be a trivial task in any language.这在任何语言中都应该是一项微不足道的任务。

Rust is preventing you from mutating the map while you are iterating over it. Rust 会阻止您在迭代地图时对其进行变异。 In most languages this is allowed, but often the behaviour is not well-defined, and removal of the item can interfere with the iteration, compromising its correctness.在大多数语言中,这是允许的,但通常行为没有明确定义,删除项目会干扰迭代,影响其正确性。

Why it is trying to move a reference?为什么它试图移动引用?

HashMap implements IntoIterator , so your loop is equivalent to : HashMap实现IntoIterator所以你的循环相当于

for (key, value) in map.into_iter() {
    println!("{} / {}", key, value);
    map.remove(key);
}

If you look at the definition of into_iter , you'll see that it takes self , not &self or &mut self .如果您查看into_iter定义,您会发现它需要self ,而不是&self&mut self Your variable map is a mutable reference, and IntoIterator is implemented for &mut HashMap - the self in into_iter is the &mut HashMap , not the HashMap .您的变量map是一个可变参考,并IntoIterator是为实现&mut HashMap -在selfinto_iter&mut HashMap ,而不是HashMap Mutable references cannot be copied (since only one mutable reference to any data can exist at one time) so this mutable reference is moved.无法复制可变引用(因为一次只能存在一个对任何数据的可变引用),因此该可变引用被移动。

The API is intentionally built that way so that you can't do anything dangerous while looping over a structure. API 是有意以这种方式构建的,因此您在遍历结构时不能做任何危险的事情。 Once the loop is complete, the ownership of the structure is relinquished and you can use it again.循环完成后,结构的所有权将被放弃,您可以再次使用它。

One solution is to keep track of the items you intend to remove in a Vec and then remove them afterwards:一种解决方案是跟踪您打算在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);
    }
}

You may also use an iterator to filter the map into a new one.您还可以使用迭代器将地图过滤为新的地图。 Perhaps something like this:也许是这样的:

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();
}

But I just saw Shepmaster's edit - I had forgotten about retain , which is better.但我刚刚看到 Shepmaster 的编辑 - 我忘记了retain ,这是更好的。 It's more concise and doesn't do unnecessary copying as I have done.它更简洁,不会像我所做的那样进行不必要的复制。

rust actually supports a wide number of potential solutions to this problem, though i myself also found the situation to be a bit confusing at first, and again each time i need a more complicated treatment of my hashmaps. Rust 实际上支持针对这个问题的大量潜在解决方案,尽管我自己也发现这种情况起初有点令人困惑,而且每次我需要对我的哈希图进行更复杂的处理时。

  • to iterate through items while removing them, use .drain() .要在删除项目时迭代它们,请使用.drain() .drain() has the advantage of taking/owning rather than borrowing the values. .drain()具有获取/拥有而不是借用值的优势。
  • if you want to only conditionally remove some of them, use .drain_filter() .如果您只想有条件地删除其中一些,请使用.drain_filter()
  • if you need to mutate every item but only want to remove some of them, you may mutate them in .drain_filter() 's closure argument, but this will check for removal after the changes.如果您需要改变每个项目但只想删除其中的一些,您可以在.drain_filter()的闭包参数中改变它们,但这将在更改检查是否删除。
  • if you need to check for removal before the changes, use a variable to store the result of the check, then return that variable at the end.如果您需要在更改之前检查删除,请使用一个变量来存储检查结果,然后在最后返回该变量。 a very slightly slower but maybe more clear alternative is just to mutate them in one for loop, then .drain_filter() them in another for loop or a map.一个稍微慢一点但可能更清楚的替代方法是在一个 for 循环中改变它们,然后在另一个 for 循环或映射中.drain_filter()它们。
  • you may also simply allow the hashmap to drop at the end of the function by not borrowing it in the function argument, and initialize a new one if you need it.您也可以通过不在函数参数中借用它来简单地允许哈希图在函数末尾删除,并在需要时初始化一个新的。 this completely removes the hashmap, obviously.显然,这完全删除了哈希图。 obviously you might want to keep the hashmap around so you don't reinitialize it over and over.显然,您可能希望保留散列图,这样您就不会一遍又一遍地重新初始化它。
  • you may also call .clear() to remove all elements, after you're done iterating through them to print them.你也可以调用.clear()来删除所有元素,在你完成遍历它们以打印它们之后。

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

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