Newtype idiom hashset get

I'm using the newtype in my collections wrapper. Like you can see in the following playground: Rust Playground.

The issue arise here:

    ...
        fn get<K, Q>(&self, key: &K) -> Option<&ItemName>
        where
            K: Borrow<Q>,
            Q: Into<ItemName>
        {
            self.items.get(key.into())
        }
    ...

From what I understand, my type is missing some conversion methods but I don't see how to solve the issue...

error[E0277]: the trait bound `&ItemName: From<&K>` is not satisfied
  --> src/lib.rs:30:28
   |
30 |         self.items.get(key.into())
   |                            ^^^^ the trait `From<&K>` is not implemented for `&ItemName`
   |
   = note: required because of the requirements on the impl of `Into<&ItemName>` for `&K`

So the question would be what kind of types you want to support as key in the get method. If it’s “either &ItemName or &str” then you could add a Borrow<str> for ItemName implementation and make it work like this

impl Borrow<str> for ItemName {
    fn borrow(&self) -> &str {
        &self.0
    }
}

impl Store {
    fn get<K, Q>(&self, key: &Q) -> Option<&ItemName>
    where
        ItemName: Borrow<Q>,
        Q: Hash + Eq,
    {
        self.items.get(key)
    }
}

(playground)

If you also want to support &String, you can add a Borrow<String> for ItemName implementation analogous to the Borrow<str> one.

2 Likes

My usecase is the following: I want to be able to write:

pub fn main() {
    let store = Store { items: HashSet::new() };
    store.insert("pineapple");
    let pineapple = store.get("pineapple");
}

I tried with your given playground, but I got the following error:

error[E0277]: the size for values of type `str` cannot be known at compilation time
  --> src/main.rs:43:31
   |
43 |     let pineapple = store.get("pineapple");
   |                               ^^^^^^^^^^^ doesn't have a size known at compile-time
   |
   = help: the trait `Sized` is not implemented for `str`

Then, by following the HashSet standard implementation I added the following:

fn get<K, Q: ?Sized>(&self, key: &Q) -> Option<&ItemName>

But I then get another error:

error[E0282]: type annotations needed
  --> src/main.rs:43:27
   |
43 |     let pineapple = store.get("pineapple");
   |                           ^^^ cannot infer type for type parameter `K` declared on the associated function `get`

Not sure how I can get my desired behaviour.

PS: In this code snippet, is K necessary? I think it's the first time I see a where not starting with a generic type name. How does it works?

    fn get<K, Q>(&self, key: &Q) -> Option<&ItemName>
    where
        ItemName: Borrow<Q>,
        Q: Hash + Eq,
    {
        self.items.get(key)
    }

The definition of get() isn't quite right. It should be:

    fn get<Q:?Sized>(&self, key: &Q) -> Option<&ItemName>
    where
        ItemName: Borrow<Q>,
        Q: Hash + Eq,
    {
        self.items.get(key)
    }

(Updated playground)


It works just the same as with a generic type in that position. Calling get() is only allowed if the type ItemName implements the trait Borrow<Q>.

1 Like

Thanks it now works.

    where
        ItemName: Borrow<Q>,
        Q: Hash + Eq,

In this code I don't understand the ItemName part, I thought only generics name could be placed on the left part of the colon. What does it mean and how does it work?

Ah, thanks for fixing it. I really should’ve tested it myself xD

1 Like

Well, it’s not true. Any type can go on the left side of the colon in a where clause, it just very commonly is a generic name. OTOH in the generic parameters list (i.e. between the <> / so not in the where clause) indeed ony a generic parameter name can go on the left side of the colon.

It means that only substitutions for Q can be used such that ItemName implements Borrow<Q> and Q implements Hash and Eq.

With the fixed Q: ?Sized version of the code, this means that e.g. str and ItemName are both okay since:

  • ItemName derives Eq and Hash, so that with Q == ItemName, Q: Hash + Eq isfulfilled,
    and also there’s a generic implementation impl<T> Borrow<T> for T in the standard library so that indeed with Q == ItemName, we have ItemName: Borrow<Q> i.e. ItemName: Borrow<ItemName> as a special case of the generic implementation, obtained by substituting T == ItemName in T: Borrow<T>.
  • str is an unsized type, hence the ?Sized qualifier is necessary for it to be usable. With Q == str, you have Q: Hash + Eq, i.e. str: Hash + Eq from the standard library (here and here), and there’s the impl Borrow<str> for ItemName implementation in the playground that provides ItemName: Borrow<str>.
2 Likes