Reputation: 10581
I have two HashMap<&str, String>
with the same keys and I wish to create one HashMap
with same keys where the values are combined. I do not want to keep references to the first two HashMap
s, but want to move the String
s to the new HashMap
.
use std::collections::HashMap;
#[derive(Debug)]
struct Contact {
phone: String,
address: String,
}
fn main() {
let mut phones: HashMap<&str, String> = HashMap::new();
phones.insert("Daniel", "798-1364".into());
phones.insert("Ashley", "645-7689".into());
phones.insert("Katie", "435-8291".into());
phones.insert("Robert", "956-1745".into());
let mut addresses: HashMap<&str, String> = HashMap::new();
addresses.insert("Daniel", "12 A Street".into());
addresses.insert("Ashley", "12 B Street".into());
addresses.insert("Katie", "12 C Street".into());
addresses.insert("Robert", "12 D Street".into());
let contacts: HashMap<&str, Contact> = phones.keys().fold(HashMap::new(), |mut acc, value| {
acc.entry(value).or_insert(Contact {
phone: *phones.get(value).unwrap(),
address: *addresses.get(value).unwrap(),
});
acc
});
println!("{:?}", contacts);
}
But I have an error
error[E0507]: cannot move out of a shared reference
--> src/main.rs:24:20
|
24 | phone: *phones.get(value).unwrap(),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ move occurs because value has type `std::string::String`, which does not implement the `Copy` trait
error[E0507]: cannot move out of a shared reference
--> src/main.rs:25:22
|
25 | address: *addresses.get(value).unwrap(),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ move occurs because value has type `std::string::String`, which does not implement the `Copy` trait
Upvotes: 10
Views: 6865
Reputation: 28055
HashMap::get
returns an Option<&V>
, that is, a reference to the value inside the map. You cannot move out of a reference with *
unless V
implements Copy
. You need a different method that moves the value out of the map, which is HashMap::remove
(note that it returns Option<V>
).
If you try to rewrite the same algorithm using remove
, you'll get a different error:
let contacts: HashMap<&str, Contact> = phones.keys().fold(HashMap::new(), |mut acc, value| {
acc.entry(value).or_insert(Contact {
phone: phones.remove(value).unwrap(),
address: addresses.remove(value).unwrap(),
});
acc
});
error[E0502]: cannot borrow `phones` as mutable because it is also borrowed as immutable
--> src/main.rs:22:79
|
22 | let contacts: HashMap<&str, Contact> = phones.keys().fold(HashMap::new(), |mut acc, value| {
| ------ ---- ^^^^^^^^^^^^^^^^ mutable borrow occurs here
| | |
| | immutable borrow later used by call
| immutable borrow occurs here
23 | acc.entry(value).or_insert(Contact {
24 | phone: phones.remove(value).unwrap(),
| ------ second borrow occurs due to use of `phones` in closure
error: aborting due to previous error
For more information about this error, try `rustc --explain E0502`.
This error is telling you that you can't mutate a data structure while iterating over it, because mutating the data structure may invalidate the iterator. Sometimes you can solve this with interior mutability, but in this case you don't need to do anything like that. Just call phones.into_iter()
to move the phone numbers out of the map while you iterate. Then it's easy to use a map
to create (&str, Contact)
tuples and, finally, collect
it all back into a HashMap
.
let contacts: HashMap<_, _> = phones
.into_iter()
.map(|(key, phone)| {
(
key,
Contact {
phone,
address: addresses.remove(key).unwrap(),
},
)
})
.collect();
Upvotes: 12
Reputation: 14002
The zip
is your friend here. But the "business logic" here dictates that it only works for a sorted map. So you if can use BTreeMap
instead of HashMap
, the following would work:
fn main() {
let mut phones: BTreeMap<&str, String> = BTreeMap::new();
...
let mut addresses: BTreeMap<&str, String> = BTreeMap::new();
...
let contacts: BTreeMap<&str, Contact> = phones
.into_iter()
.zip(addresses.into_iter())
.map(|((name, phone), (_, addr))| {
(
name,
Contact {
phone: phone,
address: addr,
},
)
})
.collect();
println!("{:#?}", contacts);
}
Upvotes: 1