I started with this non-generic function which works fine:
pub fn combine_hash_map<i32, i32>(a: HashMap<i32, i32>, b: HashMap<i32, i32>) -> HashMap<i32, i32> {
let mut combined = a.clone();
combined.extend(b);
combined
}
Then I tried to convert it to a generic function:
pub fn combine_hash_map<K, V>(a: HashMap<K, V>, b: HashMap<K, V>) -> HashMap<K, V>
Rust gives me detailed and very helpful error message that I need trait bounds. So I added them:
pub fn combine_hash_map<K: Clone, V: Clone>(a: HashMap<K, V>, b: HashMap<K, V>) -> HashMap<K, V>
Now that Rust simply tells me:
combined.extend(b);
^^^^^^ method not found in `HashMap<K, V>`
What is wrong? Does the compiler have the information to give a more helpful message, like the trait bound error?
The message isn't too helpful indeed. You're missing Eq + Hash
bounds on K
which are necessary for almost all HashMap
operations. Also, the cloning seems redundant in the first place, so you could remove the .clone()
call and the : Clone
bounds. Here's some version of the code that compiles:
use std::hash::Hash;
use std::collections::HashMap;
pub fn combine_hash_map<K: Eq + Hash, V>(a: HashMap<K, V>, b: HashMap<K, V>) -> HashMap<K, V> {
let mut combined = a;
combined.extend(b);
combined
}
3 Likes
Thank you!
I'm constantly amazed by how fast questions on Rust forum got answers! I don't even need to turn on notifications, just keep the browser tab open and check back in a few minutes. 
I want clone
because I don't want to change a
. Is the clone
call wrong for that purpose? I don't want to change b
either so I should pass b.clone()
to extend
.
Rust need to provide the same level of error message just like the Clone
trait bound message.
BTW: I used IDE's "go to definition" which opened the source of HashMap
, which shows the needed trait bound. Looks the information is at least available for Rust to improve messaging.
2 Likes
Keeping a
unmodified only makes sense if the combine_hash_map
function wouldn't take them owned / by-value. Or maybe your code example is just to highlight code that's part of a larger function. You can certainly add Clone
bounds to K
and V
and work with &HashMap<K, V>
arguments. However .extend()
won't accept &b
unless the key and value types are Clone
(which is a design decision to make sure that potentially expensive Clone
Ing is more explicit). The (explicitly cloning the elements) way to pass b
on this case is to use something like .extend(b.iter().map(|(k, v)| (k.clone(), v.clone())))
. Here's the full example
use std::hash::Hash;
use std::collections::HashMap;
pub fn combine_hash_map<K: Clone + Eq + Hash, V: Clone>(a: &HashMap<K, V>, b: &HashMap<K, V>) -> HashMap<K, V> {
let mut combined = a.clone();
combined.extend(b.iter().map(|(k, v)| (k.clone(), v.clone())));
combined
}
If you restricted it to Copy
types then the .iter().map(...)
wouldn't be necessary, e. g.
use std::collections::HashMap;
use std::hash::Hash;
pub fn combine_hash_map<K: Copy + Eq + Hash, V: Copy>(a: &HashMap<K, V>, b: &HashMap<K, V>) -> HashMap<K, V> {
let mut combined = a.clone();
combined.extend(b);
combined
}
"Explicit" seems not necessary? I simply use combined.extend(b.clone())
and it seems to work as expected. After calling let c = combine_hash_map(&a, &b)
, I called print!("{:?}\n{:?}\n{:?}", a, b, c)
and it prints all 3 hash maps properly. I think HashMap::clone
is already doing k.clone()
and v.clone()
internally.
Using b.clone()
works, but it does a little bit more work, creating another copy of the second hashmap, only to turn it into an iterator afterwards anyway.
I think .iter().cloned()
would also work.
Not for an iterator of (&K, &V)
, like hash_map::Iter
but I did initially want to suggest the same, too, before realizing that.
1 Like
You are right. I was thinking about the same thing: a wasted HashMap with b.clone
.
The explicit way can be shortened a little bit b.iter().map(|p| p.clone())
.
That's essentially the same suggestion as b.iter().cloned()
and it doesn't work.
error[E0271]: type mismatch resolving `<[closure@src/lib.rs:5:34: 5:47] as FnOnce<((&K, &V),)>>::Output == (K, V)`
--> src/lib.rs:5:21
|
3 | pub fn combine_hash_map<K: Clone + Eq + Hash, V: Clone>(a: &HashMap<K, V>, b: &HashMap<K, V>) -> HashMap<K, V> {
| - this type parameter
4 | let mut combined = a.clone();
5 | combined.extend(b.iter().map(|p| p.clone()));
| ------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected type parameter `K`, found `&K`
| |
| required by a bound introduced by this call
|
= note: expected tuple `(K, V)`
found tuple `(&K, &V)`
= note: required because of the requirements on the impl of `Iterator` for `Map<std::collections::hash_map::Iter<'_, K, V>, [closure@src/lib.rs:5:34: 5:47]>`
note: required by a bound in `extend`
The p
is (&K, &V)
and there's no way to turn this into (K, V)
without cloning both components I individually. Cloning the whole thing would only work to turn &(K, V)
into (K, V)
.
1 Like