This should be a trivial task in any language. This isn't working in 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.
To access values from HashMap using keys: Import HashMap. Insert records in HashMap. Use get( & key) for getting the value.
We access values in a hashmap by using the get() method with the key of the value we want to access. The example above will print the values associated with the “NV” and “NY” keys. When we try to access an element that doesn't exist with get(), it will return None (Rust's version of null) instead of panicking.
There are at least two reasons why this is disallowed:
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
.
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. 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. 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:
Copy
traitfor
loops take the value to iterate over by value When you call for (k, v) in map {}
, the ownership of map
is transferred to the for loop and is now gone.
I'd perform an immutable borrow of the map (&*map
) and iterate over that. 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"
I'd use 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.
This should be a trivial task in any language.
Rust is preventing you from mutating the map while you are iterating over it. 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:
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
. 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
. 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. 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:
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. It's more concise and doesn't do unnecessary copying as I have done.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With