So I'm a bit stuck, trying to merge two HashMaps.
It's easy to do it 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);
}
It's easy enough 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 to work:
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 on play.rust-lang.org.
Compiling it:
error: the trait `core::kinds::Sized` is not implemented for the type `str`
I get that the compiler is confused about the size of the generic value, but I'm not sure why "str" doesn't have a strict memory size? I know its a String slice and not a type, but still this should work, no? Is this a bug?
I thought this would be a relatively trivial function. If someone has a good solution, I'd love to learn. Actually ideally, I'd love to see a solution with a trait Mergeable
and write a decorator for HashMap<&K, &V>, such that I can call let new_context = first_context.merge(&second_context);
but this can be a different question.
A more up to date answer from this tweet:
Rust Playground Link
This version does work:
The difference is in the signature of
merge()
. Here is yours:Here is mine:
For some reason you are trying to abstract
HashMap<&str, &str>
toHashMap<&K, &V>
, but this is not really correct: while&str
is a borrowed pointer, it is special - it points to dynamically sized typestr
. Size ofstr
is not known to the compiler, so you can use it only through a pointer. Consequently, neitherHash
norEq
are implemented forstr
, they are implemented for&str
instead. Hence I've changedHashMap<&'a K, &'a V>
toHashMap<K, V>
.The second problem is that in general you can't write your function if it takes only references to maps. Your non-generic merge function works only because
&str
is a reference and references are implicitly copyable. In general case, however, both keys and values can be non-copyable, and merging them into the single map will require moving these maps into the function. AddingCopy
bound allows that.You can also add
Clone
bound instead ofCopy
and use explicitclone()
call:The most general way, however, is moving maps into the function:
Note
into_iter()
method which consumes the map, but returns an iterator of tuples with actual values instead of references.