Not so helpful error message "method not found"

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. :grinning:

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 CloneIng 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