Unexpected type inferred as HashMap key

I have a struct generic for type C holding a HashMap where the key is C::Owned:

This doesn't compile:

pub struct Collection<C: Hash + Eq + ToOwned> where C::Owned: Hash + Eq + Clone {
    items: HashMap<C::Owned, String>
}
impl <C: Hash + Eq + ToOwned> Collection<C> where C::Owned: Hash + Eq + Clone {
    pub fn has_item(&mut self, concern: &C::Owned) -> bool {
        self.items.contains_key(concern)
    }
}

The compiler error:

error[E0308]: mismatched types
   --> src/error.rs:439:33
    |
439 |         self.items.contains_key(concern)
    |                                 ^^^^^^^ expected type parameter, found associated type
    |
    = note: expected type `&C`
               found type `&<C as std::borrow::ToOwned>::Owned`

This works:

pub struct Collection<C: Hash + Eq + ToOwned> where C::Owned: Hash + Eq + Clone + Borrow<C> {
    items: HashMap<C::Owned, String>
}
impl <C: Hash + Eq + ToOwned> Collection<C> where C::Owned: Hash + Eq + Clone + Borrow<C> {
    pub fn has_item(&mut self, concern: &C::Owned) -> bool {
        self.items.contains_key(concern.borrow())
    }
}

This also works:

use std::borrow::Cow;
pub struct Collection<C: ToOwned + Hash + Eq> where C::Owned: Hash + Eq{
    items: HashMap<C::Owned, String>
}
impl <C: ToOwned + Hash + Eq> Collection<C>  where C::Owned: Hash + Eq {
    pub fn has_item(&mut self, concern: Cow<C>) -> bool {
        self.items.contains_key(concern.as_ref())
    }
}

It seems that for HashMap::contains_key the assumed type of key is C while it has been explicitly declared as C::Owned. Why is that?

By adding the following line to your imports:

use std::borrow::Borrow;

You can use the fact that ToOwned::Owned: Borrow<Self>, meaning you can use .borrow to get the appropriate reference. This is still rather odd, as though you did explicitly mark the HashMap to use C::Owned. Anyway, here's the code.

2 Likes

Can you try this ?

pub struct Collection<C: Hash + Eq + ToOwned + Sized?> where C::Owned: Hash + Eq + Clone {
    items: HashMap<C::Owned, String>
}

impl <C: Hash + Eq + ToOwned + Sized?> Collection<C> where C::Owned: Hash + Eq + Clone {
    pub fn has_item(&mut self, concern: &C) -> bool {
        self.items.contains_key(concern)
    }
}

Also I think following is a better abstraction of type-parameters and flexible API.

pub struct Collection<C: Hash + Eq> {
    items: HashMap<C, String>
}

impl <C: Hash + Eq> Collection<C> {
    pub fn has_item<Q>(&mut self, concern: &Q) -> bool
    where
        C: Borrow<Q>,
        Q: Hash + Eq,
    {
        self.items.contains_key(concern)
    }
}

UPDATE: Fixed some minor compilation issue with older snippet.

Thank you,
both of the solutions by OptimisticPeach and prataprc work.

It still bugs me why would compiler assume that C is the key. Since the second solution differs from mine only in the "?Sized" constraint, the reason is probably to be found there.

I'll check the last solution too but it reminds me upon something I have tried and abandoned for something unrelated to this topic.

Thanks again!

@lame-impala the last solution is the stdlib way of doing this :slight_smile:, only C is replaced with K and we can add ?Sized constraint to Q

The key is always C::Owned. The problem is the compilers error message. It should be saying the found type is incompatible with the generic function bounds, then optionally make some suggestions. Still probably would only suggest to use a type &C; you may really want C::Owned: Borrow<C::Owned> which is also achieved with use as @OptimisticPeach comments.

2 Likes

That was the missing piece. Thanks!

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.