Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I create a HashMap with values moved from two other HashMaps?

Tags:

hashmap

rust

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 HashMaps, but want to move the Strings 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

Playground

like image 744
jeanpaul62 Avatar asked Aug 25 '19 19:08

jeanpaul62


1 Answers

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

Playground

like image 74
trent Avatar answered Oct 01 '22 03:10

trent