2

I have the 2 following traits:

trait Filter {
    type Message;
    fn is_valid(&self, message: &Self::Message) -> bool;
}

trait Client {
    type Message;
    fn send(&self, message: &Self::Message) -> Result<(), Error>;
}

I would like an implementation of Filter and Client to use the same Message type.

struct ClientWithFilter<C: Client, F: Filter> {
    filter: F,
    client: C,
}

impl<C: Client, F: Filter> ClientWithFilter<C, F> {
    /// C::Message or F::Message???
    fn check_and_send(&self, message: &C::Message) -> Result<(), Error> {
        if self.filter.is_valid(message) {
            self.client.send(message)
        } else {
            Err(Error::MessageInvalid)
        }
    }
}

This does not compile:

if self.filter.is_valid(message) {
    |                   ^^^^^^^ expected client::Filter::Message, found client::Client::Message
    |
    = note: expected type `&<F as client::Filter>::Message`
               found type `&<C as client::Client>::Message`

The compiler sees 2 distinct types where I would like to have a single one. How can I write this in Rust in a correct way?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
bm842
  • 369
  • 2
  • 10
  • 2
    *"This does not compile"*: please add a error message then if it does not compile so we can easily reproduce your problem.. – hellow Mar 13 '19 at 07:03
  • 2
    Could you include the error message? We can't help you unless you do so. – Malekai Mar 13 '19 at 07:43

1 Answers1

8

You need to constrain the type parameters appropriately:

struct ClientWithFilter<C, F>
where
    C: Client,
    F: Filter<Message = C::Message>,
{
    filter: F,
    client: C,
}

impl<C, F> ClientWithFilter<C, F>
where
    C: Client,
    F: Filter<Message = C::Message>,
{
    fn check_and_send(&self, message: &C::Message) -> Result<(), Error> {
        if self.filter.is_valid(message) {
            self.client.send(message)
        } else {
            Err(Error::MessageInvalid)
        }
    }
}

playground

The redundant duplication of the constraints on the impl is necessary for now, I think. I believe there's an RFC to let impls inherit constraints from the struct definition.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Sebastian Redl
  • 69,373
  • 8
  • 123
  • 157
  • 1
    You can also write `>` directly (to me it doesn't seem like `where` adds much readability here). I'd like `where C::Message = F::Message` more, but it doesn't work yet: https://github.com/rust-lang/rust/issues/20041. – Alexey Romanov Mar 13 '19 at 08:00
  • 1
    To avoid the duplication of trait bounds, you could simply remove the trait bounds from the struct itself and only leave them on the impl. If the struct can only be created by private constructors the end result is the same. You only need trait bounds on the struct if you need to use associated types in the struct definition, but that's not the case here. – Sven Marnach Mar 13 '19 at 10:13