Combine two HashMaps in Rust

So, I'm a little stuck trying to combine two HashMaps.

It's easy to do this inline:

fn inline() {
    let mut first_context = HashMap::new();
    first_context.insert("Hello", "World");
    let mut second_context = HashMap::new();
    second_context.insert("Hey", "There");

    let mut new_context = HashMap::new();
    for (key, value) in first_context.iter() {
        new_context.insert(*key, *value);
    }
    for (key, value) in second_context.iter() {
        new_context.insert(*key, *value);
    }
    println!("Inline:\t\t{}", new_context);
    println!("Inline:\t\t{}\t{} [Initial Maps Still Usable]", first_context, second_context);
}

      

Easy to make a function:

fn abstracted() {
    fn merge<'a>(first_context: &HashMap<&'a str, &'a str>, second_context: &HashMap<&'a str, &'a str>) -> HashMap<&'a str, &'a str> {
        let mut new_context = HashMap::new();
        for (key, value) in first_context.iter() {
            new_context.insert(*key, *value);
        }
        for (key, value) in second_context.iter() {
            new_context.insert(*key, *value);
        }
        new_context
    }

    let mut first_context = HashMap::new();
    first_context.insert("Hello", "World");
    let mut second_context = HashMap::new();
    second_context.insert("Hey", "There");

    println!("Abstracted:\t{}", merge(&first_context, &second_context));
    println!("Abstracted:\t{}\t{} [Initial Maps Still Usable]", first_context, second_context);
}

      

However, I can't seem to get the generic version:

fn generic() {
    fn merge<'a, K: Hash + Eq, V>(first_context: &HashMap<&'a K, &'a V>, second_context: &HashMap<&'a K, &'a V>) -> HashMap<&'a K, &'a V> {
        let mut new_context = HashMap::new();
        for (key, value) in first_context.iter() {
            new_context.insert(*key, *value);
        }
        for (key, value) in second_context.iter() {
            new_context.insert(*key, *value);
        }
        new_context
    }

    let mut first_context = HashMap::new();
    first_context.insert("Hello", "World");
    let mut second_context = HashMap::new();
    second_context.insert("Hey", "There");

    println!("Generic:\t{}", merge(&first_context, &second_context));
    println!("Generic:\t{}\t{} [Initial Maps Still Usable]", first_context, second_context);
}

      

The above code is at play.rust-lang.org .

Compilation:

error: the trait `core::kinds::Sized` is not implemented for the type `str`

      

I understand that the compiler is confused about the size with the total, but I'm not sure why "str" ​​doesn't have a strict memory size? I know its a String fragment, not a type, but it should still work, no? This is mistake?

I thought it would be a relatively trivial feature. If anyone has a good solution, I would love to learn. Actually perfect, I would like to see a solution with trait Mergeable

and write a decorator for the HashMap <& K & V> so that I can call let new_context = first_context.merge(&second_context);

, but that might be a different question.

+3


source to share


2 answers


This version works:

use std::collections::HashMap;
use std::hash::Hash;

fn main() {
    fn merge<K: Hash + Eq + Copy, V: Copy>(first_context: &HashMap<K, V>, second_context: &HashMap<K, V>) -> HashMap<K, V> {
        let mut new_context = HashMap::new();
        for (key, value) in first_context.iter() {
            new_context.insert(*key, *value);
        }
        for (key, value) in second_context.iter() {
            new_context.insert(*key, *value);
        }
        new_context
    }

    let mut first_context = HashMap::new();
    first_context.insert("Hello", "World");
    let mut second_context = HashMap::new();
    second_context.insert("Hey", "There");

    println!("Generic:\t{}", merge(&first_context, &second_context));
    println!("Generic:\t{}\t{} [Initial Maps Still Usable]", first_context, second_context);
}

      

The difference lies in the signature merge()

. Here's yours:

fn merge<'a, K: Hash + Eq, V>(first_context: &HashMap<&'a K, &'a V>, second_context: &HashMap<&'a K, &'a V>) -> HashMap<&'a K, &'a V>

      

Here is my:

fn merge<K: Hash + Eq + Copy, V: Copy>(first_context: &HashMap<K, V>, second_context: &HashMap<K, V>) -> HashMap<K, V>

      

For some reason you are trying to divert HashMap<&str, &str>

before HashMap<&K, &V>

, but this is not entirely correct: while &str

is a borrowed pointer, it is special - it points to a dynamically sized type str

. The size is str

not known to the compiler, so you can only use it through a pointer. Consequently, for the str

not implemented either Hash

, no Eq

, instead they are implemented &str

. So I changed HashMap<&'a K, &'a V>

to HashMap<K, V>

.



The second problem is that you cannot write your function at all if it only takes map references. Your non-generic merge function only works because it &str

is a link and the links are implicitly copied. In general, however, both keys and values ​​may not be copyable, and merging them into a single map would require moving those maps into a function. Adding Copy

bound allows this.

You can also add Clone

bound instead Copy

and use an explicit call clone()

:

fn merge<K: Hash + Eq + Clone, V: Clone>(first_context: &HashMap<K, V>, second_context: &HashMap<K, V>) -> HashMap<K, V> {
    // ...
    for (key, value) in first_context.iter() {
        new_context.insert(key.clone(), value.clone());
    }
    // ...
}

      

The most common way, however, is to move the cards into a function:

fn merge<K: Hash + Eq, V>(first_context: HashMap<K, V>, second_context: HashMap<K, V>) -> HashMap<K, V>  {
    // ...
    for (key, value) in first_context.into_iter() {
        new_context.insert(key, value);
    }
    // ...
}

      

Note a into_iter()

method that uses a map, but returns an iterator of tuples with actual values ​​instead of references.

+5


source


More relevant answer from this tweet :

use std::collections::HashMap;

// Mutating one map
fn merge1(map1: &mut HashMap<(), ()>, map2: HashMap<(), ()>) {
    map1.extend(map2);
}

// Without mutation
fn merge2(map1: HashMap<(), ()>, map2: HashMap<(), ()>) -> HashMap<(), ()> {
    map1.into_iter().chain(map2).collect()
}

// If you only have a reference to the map to be merged in
fn merge_from_ref(map: &mut HashMap<(), ()>, map_ref: &HashMap<(), ()>) {
    map.extend(map_ref.into_iter().map(|(k, v)| (k.clone(), v.clone())));
}

      



Rust Playground Link

+4


source







All Articles