Storing differently-typed channels in a hashmap

The library I'm working on allows a user to create channels that read and write channel messages to and from the same TCP connection. I am using the async-channels crate for the channels.

The library forces a user to create a new channel for every different type, and it maintains a HashMap of the TypeId for every type and its corresponding channel. In the background, the library serializes all the different messages into bytes alongside its TypeId and writes that data to the TCP stream. On reading from the stream, it extracts the TypeId and bytes, looks up the channel from the Hashmap using the TypeId as the key, and then sends the message on the channel.

My issue is that I don't know how to store differently-typed values in the Hashmap. One relation could be TypeId -> (Sender<String, Receiver<String>), while another could be TypeId -> (Sender<CustomEnum>, Receiver<CustomEnum>).

I tried defining traits like GenericSender and GenericReceiver that implemented the same send, try_send, clone, recv, try_recv functions as Sender and Receiver structs. But I quickly learned this was not feasible, as send for example takes a generic parameter T, which would not allow me to use GenericSender or GenericReceiver as trait objects.

What is the idiomatic way of solving this problem?

Let's think about what has to actually happen in your program in order to call Sender::send with arbitrary type T determined at runtime. Even when using a trait object like GenericSender, the compiler will have to know the concrete type of the function argument to send in order to generate code for the function call. So maybe we can also use type erasure for the argument. Yet, then the callee would somehow have to check at runtime that the argument passed was the expected type, and be able to unpack it. This sounds like Any::downcast_ref to me. It seems storing Senders as Any and using Any::downcast_ref may be the solution here: Playground Link. There's also a crate called anymap which may do exactly what you want.

Side note: I'll assume you know what you're doing, but I'd be careful about serializing TypeId as it is specific to a single compiler release.

1 Like

Thanks for the thorough answer. The playground link was also incredibly helpful, I really appreciated it!