Impl for struct with generic trait bounds

I feel like this is a silly question, and that I'm missing something obvious, but after rereading the chapters on traits, generics, and advanced traits (and several SO questions and forum posts) I still cannot get my code to compile.

I have a trait HasId that only defines one method hash().
I have at least one struct that implements HasId, called ByteId.
I have a struct Token that has two fields, each that require a type that implements HasId.

I then need to create 2 impl blocks for Token, one that adds new() and a couple others, and one that implements another trait for Token.

In both cases, I keep coming up with a variety of errors, most often a type mismatch or a sized error. The issue seems to be around syntax -- how do I impl a struct when that struct has generic types that must implement traits.

An example

pub trait HasId {
    fn hash(&self) -> &String;
}

#[derive(Debug)]
pub struct ByteId {
    hash: String,
}

impl HasId for ByteId {
    fn hash(&self) -> &String {
        &self.hash
    }
}

#[derive(Debug)]
pub struct Token<U: HasId, I: HasId> {
    uuid: Option<U>,
    id: I,
}

impl<T, I> Token<T, I> 
    where T: HasId, I: HasId 
{
    pub fn new() -> Token<T, I> {
        let uuid = ByteId { hash: String::from("ID One")};
        let id = ByteId { hash: String::from("ID Two")};

        Token {
            uuid: Some(uuid),
            id,
        }
    }
}

fn main() {
    let token = Token::new();
    println!("{:?}", token);
}

Which throws a mismatch error:

error[E0308]: mismatched types
  --> src/main.rs:31:13
   |
31 |             id,
   |             ^^ expected type parameter, found struct `ByteId`
   |
   = note: expected type `I`
              found type `ByteId`

And, as soon as I posted, I tried one last thing and got it to work. I though I'd post my solution for the internet. Hopefully save someone else an hour of searching.

I had to pass the id and uuid INTO the new() method, not create those locally. This is fine. That is what I would have expected to do anyway. But, I still don't understand why ByteId wouldn't satisfy HasId if I create that variable inside the new() function.

In any case, here is my working example:

pub trait HasId {
    fn hash(&self) -> &String;
}

#[derive(Debug)]
pub struct ByteId {
    hash: String,
}

impl HasId for ByteId {
    fn hash(&self) -> &String { &self.hash}
}

#[derive(Debug)]
pub struct Token<T: HasId, I: HasId> {
    uuid: Option<T>,
    id: I,
    key: bool
}

impl<T: HasId, I: HasId> Token<T, I> 
{
    pub fn new(uuid: T, id: I) -> Token<T, I> {
        Token {
            uuid: Some(uuid),
            id,
            key: true
        }
    }
}

fn main() {
    let uuid = ByteId { hash: String::from("ID One")};
    let id = ByteId { hash: String::from("ID Two")};

    let token = Token::new(uuid, id);
    println!("{:?}", token);
}

This signature means that the method new must be able to create a Token for any combination of types T and I that implement HasId.
In other words, the caller of the method decides what types T and I are.

1 Like

@troplin, well that seems so obvious it's almost embarrassing, lol. Thanks for the explanation.

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