the type parameter `T` is not constrained by the impl trait, self type, or predicates

Viewed 2524

I'm struggling to wrap my head around usage of traits when these have associated types.

Here's a very simplified example:

pub trait Message {}

pub trait SendsMessages {
    type Message: Message;
    
    fn send(msg: Self::Message);
}

pub struct ImplementsSendsMessages {

}

impl<T> SendsMessages for ImplementsSendsMessages
where
    T: Message,
{
    type Message = T;

    fn send(id: T) {
        todo!()
    }
}

Can anyone point me to what I'm doing wrong?

1 Answers

Whenever you write impl<T>, you're defining a generic implementation — a family of possible implementations. For every type you can fill in for T, that's a different implementation.

But every trait implementation must be uniquely identifiable from the trait and the implementing type: you can't have more than one SendsMessages for ImplementsSendsMessages, and that's what your code is trying to create.

You can define the trait with a type parameter instead of an associated type:

pub trait Message {}

pub trait SendsMessages<M: Message> {
    fn send(msg: M);
}

pub struct ImplementsSendsMessages {}

impl<T> SendsMessages<T> for ImplementsSendsMessages
where
    T: Message,
{
    fn send(id: T) {
        todo!()
    }
}

This is accepted because there's a different trait SendsMessages<T> for each T that exists, so it's unambiguous.

(It would also work if you had struct ImplementsSendsMessage<T>, since that's also unambiguous.)

Another way to look at this is that the purpose of associated types as opposed to type parameters is to be “outputs”, rather than “inputs”: once you pick a trait, an implementing type, and fill in all the parameters, the associated type is always known and unique. Your code fails because it's trying to fill in the associated type with “anything”, when it has to be a single concrete type.

Related