I am working on a implementation of the observer design pattern where each observer is tied to a concrete type which should be received on notify. See the following minimal example:
use std::{collections::HashMap, sync::Arc};
pub trait Message {
fn id() -> u8
where
Self: Sized;
}
pub trait Subscriber<T>
where
T: Message + ?Sized,
{
fn receive(&self, msg: &Box<T>);
}
pub struct Publisher<'a, 'b> {
subscribers: HashMap<
u8,
(
fn(&[u8]) -> Box<dyn Message + 'a>,
Arc<dyn Subscriber<dyn Message + 'a> + 'b>,
),
>,
}
impl<'a, 'b> Publisher<'a, 'b> {
pub fn new() -> Self {
Self {
subscribers: HashMap::new(),
}
}
pub fn register_subscriber<T>(&mut self, subscriber: Arc<dyn Subscriber<T> + 'b>)
where
T: Message + 'a,
T: for<'c> From<&'c [u8]>,
{
self.subscribers
.insert(T::id(), (|data| Box::new(T::from(data)), subscriber));
}
pub fn publish(&self, id: u8, data: &[u8]) {
if let Some((message_factory, subscriber)) = self.subscribers.get(&id) {
let message = message_factory(data);
subscriber.receive(&message);
}
}
}
pub struct ConcreteMessage {
// members omitted for simplicity
}
impl Message for ConcreteMessage {
fn id() -> u8 {
12
}
}
impl From<&[u8]> for ConcreteMessage {
fn from(_data: &[u8]) -> Self {
// decode the u8 data and save the info in
// ConcreteMessage members (omitted for simplicity)
Self {}
}
}
pub struct ConcreteSubscriber {
// members omitted for simplicity
}
impl Subscriber<ConcreteMessage> for ConcreteSubscriber {
fn receive(&self, _msg: &Box<ConcreteMessage>) {
// process the message (omitted for simplicity)
}
}
fn main() {
let mut publisher = Publisher::new();
let subscriber = Arc::new(ConcreteSubscriber {});
publisher.register_subscriber(subscriber);
publisher.publish(12, &[0, 1, 2, 3]);
}
This fails to compile with the following error:
error[E0308]: mismatched types
--> src/main.rs:39:63
|
33 | pub fn register_subscriber<T>(&mut self, subscriber: Arc<dyn Subscriber<T> + 'b>)
| - found this type parameter
...
39 | .insert(T::id(), (|data| Box::new(T::from(data)), subscriber));
| ^^^^^^^^^^ expected `Arc<dyn Subscriber<dyn Message>>`, found `Arc<dyn Subscriber<T>>`
|
= note: expected struct `Arc<(dyn Subscriber<(dyn Message + 'a)> + 'b)>`
found struct `Arc<(dyn Subscriber<T> + 'b)>`
= help: type parameters must be constrained to match other types
= note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters
I don't really know how to make it work, any help would be highly appreciated.
The short answer is that dyn Subscriber<(dyn Message + 'a)>
is a type which can receive ANY message type, not a specific message type. You cannot cast dyn Subscriber<T>
to dyn Subscriber<(dyn Message + 'a)>
, in your example, ConcreteSubscriber
certainly cannot handle arbitrary message types, so if this case was somehow valid, what would be passed as argument to ConcreteSubscriber::receive
?
The longer answer is that for this to work, you must create a type-erased version of the Subscriber
trait. Then you can create a blanket impl for that type-erased trait which covers all possible concrete subscribers.
Your type-erased trait is very similar to Subscriber:
trait DynSubscriber
{
fn receive_dyn(&self, msg: &[u8]);
}
You implement this as follows for ALL subscriber types:
struct DynSubscriberImpl1<'b, M : Message>{
inner : Arc<dyn Subscriber<M> + 'b>,
}
impl <'b, M : Message + for<'c> From<&'c [u8]>>
DynSubscriber for
DynSubscriberImpl1<'b, M>
{
fn receive_dyn(&self, msg: &[u8])
{
let message : M = M::from(msg);
self.inner.receive(&message);
}
}
impl <'b, M : Message > DynSubscriberImpl1<'b, M> {
fn new<S : Subscriber<M> + 'b>(inner : Arc<S>) -> Box<Self>
{
Box::new(DynSubscriberImpl1 {
inner : inner,
})
}
}
In the implementation of receive_dyn
, you know the concrete message type, so here is where you must invoke from
.
You need one more level of type erasure:
struct DynSubscriberImpl<'b> {
inner : Box<dyn DynSubscriber + 'b>,
}
impl <'b> DynSubscriberImpl<'b> {
fn new<'a : 'b, M : Message + 'a + for<'c> From<&'c [u8]>, S : Subscriber<M> + 'b>
(inner : Arc<S>) -> Self
{
DynSubscriberImpl {
inner : DynSubscriberImpl1::new(inner),
}
}
}
The type erasure is actually happening in two places - in two implementations of new
, where Arc<S>
becomes Arc<dyn ...>
. This is because you have two types which need to be erased, the message type, and the subscriber type (in other words, you need to perform double dispatch).
After this, the rest is pretty easy:
pub struct Publisher<'b> {
subscribers: HashMap<u8, DynSubscriberImpl<'b>>,
}
impl<'b> Publisher<'b> {
pub fn new() -> Self {
Self {
subscribers: HashMap::new(),
}
}
pub fn register_subscriber<'a : 'b, T, S : Subscriber<T> + 'b>(&mut self, subscriber: Arc<S >)
where
T: Message + 'a,
T: for<'c> From<&'c [u8]>,
{
self.subscribers.insert(T::id(), DynSubscriberImpl::new(subscriber));
}
pub fn publish(&self, id: u8, data: &[u8]) {
if let Some(subscriber) = self.subscribers.get(&id) {
subscriber.inner.receive_dyn(data);
}
}
}
Full version here.
A couple final notes on this approach:
pub trait Subscriber<T>
where
T: Message + ?Sized,
{
fn receive(&self, msg: &T);
}
In reality, you would probably want DynSubscriberImpl::inner
to be a Vec
since there are likely many subscribers for one message type.
This supports a subscriber accepting multiple message types, but you have to register each message type individually, and explicitly ask for the right one:
impl Subscriber<ConcreteMessage> for ConcreteSubscriber {
...
}
impl Subscriber<ConcreteMessage1> for ConcreteSubscriber {
...
}
fn main() {
let mut publisher = Publisher::new();
let subscriber = Arc::new(ConcreteSubscriber {});
// with two impls in scope, the below calls to register_subscriber are ambiguous unless you provide the message type explicitly
publisher.register_subscriber::<ConcreteMessage, _>(subscriber);
publisher.register_subscriber::<ConcreteMessage1, _>(subscriber);
}