I would like to pass events (Event
s in the example below) though message queues which can carry arbitrary payload (Event::payload
). I intend to use trait objects and the Any
trait for this.
This is what I do, basically:
pub trait EventPayload: Any + Debug {}
impl<T: ?Sized + Any + Debug> EventPayload for T {}
#[derive(Clone, Debug)]
pub struct Event {
pub payload: Arc<dyn EventPayload>,
// more fields go here
}
So some 3rd party crate can simply define:
#[derive(Debug)]
struct SomeEvent;
And some function could look like this:
fn some_handler(event: Event) {
// I can make use of the `Debug` trait:
println!("DEBUG: {event:?}");
// But these fail:
//if event.payload.is::<SomeEvent>() {
//if (&*event.payload as &dyn Any).is::<SomeEvent>() {
// And this compiles but produces the wrong output:
if (&event.payload as &dyn Any).is::<SomeEvent>() {
println!("Got SomeEvent.");
} else {
println!("Got a different event.");
}
}
fn main() {
some_handler(Event { payload: Arc::new(SomeEvent) });
some_handler(Event { payload: Arc::new(()) });
}
But as you see, it doesn't work:
DEBUG: Event { payload: SomeEvent }
Got a different event.
DEBUG: Event { payload: () }
Got a different event.
I assume that's because Arc<dyn EventPayload>
is a different type than SomeEvent
.
There is a workaround I found, but I'm not really happy with it:
pub trait EventPayload: Any + Debug {
fn type_id(&self) -> TypeId {
Any::type_id(self)
}
}
fn some_handler(event: Event) {
println!("DEBUG: {event:?}");
if Any::type_id(&*event.payload) == Any::type_id(&SomeEvent) {
println!("Got SomeEvent.");
} else {
println!("Got a different event.");
}
}
Output:
DEBUG: Event { payload: SomeEvent }
Got SomeEvent.
DEBUG: Event { payload: () }
Got a different event.
However, this doesn't allow downcasting, plus it's uglier.
I have several questions:
- Can I use an
Arc<dyn EventPayload>
useAny
's methods somehow to do type checks and downcasting? Or do I need to implement those methods redundantly onEventPayload
? If I have to do it, how can I implementdowncast
, which has a type argument and is thus not object safe? - Is using an
Arc
the idiomatic way to permit cloning of unknown/dynamic types? (assuming I won't mutate those objects) - Is downcasting considered to be sound at all? I'm asking because of #10389.