Design for receiving different types of messages via mpsc::Receiver

Hi all!

I am currently trying to develop a small library and got stuck on following problem (simplified):

I am trying to build a library which supports a customizable async "server" based on mpsc:Receiver's. The user should be able to implement handlers for specific message types, which are just user-defined structs or enums tagged with a marker trait. The server should provide a send(msg: M) method to send messages of any supported type to the server, which should in turn run the corresponding user-implemented handler method.

Up to now I have found following two possible designs:

  • Server has one Receiver. The user has to wrap all messages he wants to support into an enum M and match in the handler function against the enum to find the message type, extract the data and run the handler code. So there would be one large Handler which handles all types.
  • Server holds multiple Receiver's, one for each supported message type. The handlers could be stored in e.g. a HashMap with TypeId of the message type as key.

My preferred API would look something like:

struct UserDefinedMsg1 {}
impl SupportedMessage for UserDefinedMsg1 {}	// marker trait for user-defined messages

struct UserDefinedMsg2 {}
impl SupportedMessage for UserDefinedMsg2 {}

struct UserServer {
// some  members
}

impl Handler<UserDefinedMsg1> for UserServer {
	fn handle(&self, msg: UserDefinedMsg1) {
		// ...
	}
}  

impl Handler<UserDefinedMsg2> for UserServer {
	fn handle(&self, msg: UserDefinedMsg2) {
		// ...
	}
}  

The UserServer would then be wrapped into an internal struct by the lib in order to add the needed Receivers + run-logic. In this case, however, I see no possibility to find out for which
types the user defined the Handler trait such that the corresponding Receivers could be created in the server-wrapper. Further, there is no direct link between send(msg: M) on the server wrapper
and the Receiver's which have to be stored somewhere. So the problem basically boils down to how to store/use generic structs of different types in one struct and how to dispatch to them based on the type.

Am I missing out on something here and do you maybe see any other possible designs which would solve this problem? Sorry if my approaches are not very rustic, I am very new to the new language.

Thanks a lot and best regards!

This looks like 'enum' to me.

That's what I thought, so there is no workaround in order to avoid having the user define a wrapper enum around all the types he wants to handle? I already thought about generating the enum using a macro however there seems to be no way to find out which traits are defined on a struct from within a macro..

Rust is designed for adding a new trait to a type to never be a breaking change. One unfortunate effect of this is that it structurally impossible (or at least very hard) to have any observable effect of a trait not being implemented (other than a compile failure) — Otherwise, adding an implementation for a trait might unexpectedly change the behavior of programs that were detecting the trait as non-implemented.

I understand, I had the idea of implementing the Handlers like this because actix does something similiar, however I could not really figure out how they internally handle those different types which is why I was wondering whether there is something like a design pattern to achieve this behavior.