2
votes

I have a messaging system and want to solve everything generically. Messages can be sent to entities and the entities can handle the messages.

// There are many messages that implement this trait
trait Message {
    type Response;
}

// Messages can be sent to 'entities'
trait Entity {
    type Error;
}

// Entities can implement handlers for specific messages
trait MessageHandler<M: Message>: Entity {
    fn handle(
        &mut self,
        message: M,
    ) -> Result<M::Response, Self::Error>;
}

This would be implemented like so:

struct SimpleEntity;
impl Entity for SimpleEntity {
    type Error = ();
}

struct SimpleMessage;
impl Message for SimpleMessage {
    type Response = ();
}

impl MessageHandler<SimpleMessage> for SimpleEntity {
    fn handle(
        &mut self,
        message: SimpleMessage,
    ) -> Result<(), ()> {
        Ok(())
    }
}

All entities are stored in a system. The system can only store entities of 1 type. For every message handler that the type has, there should be a send_message function that takes the message generically.

I imagine it could look like this:

// A message system for one type of entity. This is an example. Normally there's all kinds of async multithreaded stuff here
struct MessageSystem<E: Entity> {
    handlers: Vec<E>,
}

// For every message handler, we want to implement the send_message function
impl<M: Message, MH: MessageHandler<M>> MessageSystem<MH> {
    pub fn send_message(&mut self, entity_id: (), message: M) -> Result<M::Response, MH::Error> {
        unimplemented!();
    }
}

This could then be used like so:

// Example usage
fn main() {
    let mut system = MessageSystem { handlers: vec![SimpleEntity] };
    system.send_message((), SimpleMessage).unwrap();
}

However, this gives a compile error in the impl block for the send_message function:

error[E0207]: the type parameter `M` is not constrained by the impl trait, self type, or predicates
  --> src/lib.rs:25:6
   |
25 | impl<M: Message, MH: MessageHandler<M>> MessageSystem<MH> {
   |      ^ unconstrained type parameter

error: aborting due to previous error

For more information about this error, try `rustc --explain E0207`.

Link to playground

How could I make this work?

The goal is to have these different message structs, have them be handled by a handler that an entity can implement and send the messages to the entities via the system struct.

An obvious thing would be to make the message an associated type in the MessageHandler trait, but then you can't implement multiple versions of it for an entity.

1

1 Answers

4
votes

Since M is generic to send_message but not the MessageSystem trait itself, move it to the send_message function, and move the trait bound to the method.

impl<MH: Entity> MessageSystem<MH> {
    pub fn send_message<M>(&mut self, entity_id: (), message: M) -> Result<M::Response, MH::Error>
    where
        M: Message,
        MH: MessageHandler<M>,
    {
        unimplemented!();
    }
}

Playground link

Your original error happens because you have a generic parameter that the trait doesn't use, which means it's vague and the correct impl can't be chosen.