rust 61 lines · 8 steps

A type-safe event bus in Rust

How type erasure and TypeId let one registry route events to strongly-typed handlers.

Explained by highlit
1use std::any::{Any, TypeId};
2use std::collections::HashMap;
3 
4pub trait Event: Any {
5 fn as_any(&self) -> &dyn Any;
6}
7 
8pub trait Handler<E: Event>: Send + Sync {
9 fn handle(&self, event: &E);
10}
11 
12trait ErasedHandler: Send + Sync {
13 fn call(&self, event: &dyn Any);
14}
15 
16struct HandlerWrapper<E, H> {
17 handler: H,
18 _marker: std::marker::PhantomData<fn(E)>,
19}
20 
21impl<E: Event, H: Handler<E>> ErasedHandler for HandlerWrapper<E, H> {
22 fn call(&self, event: &dyn Any) {
23 if let Some(concrete) = event.downcast_ref::<E>() {
24 self.handler.handle(concrete);
25 }
26 }
27}
28 
29#[derive(Default)]
30pub struct Registry {
31 handlers: HashMap<TypeId, Vec<Box<dyn ErasedHandler>>>,
32}
33 
34impl Registry {
35 pub fn new() -> Self {
36 Self { handlers: HashMap::new() }
37 }
38 
39 pub fn subscribe<E, H>(&mut self, handler: H)
40 where
41 E: Event,
42 H: Handler<E> + 'static,
43 {
44 let wrapper = HandlerWrapper::<E, H> {
45 handler,
46 _marker: std::marker::PhantomData,
47 };
48 self.handlers
49 .entry(TypeId::of::<E>())
50 .or_default()
51 .push(Box::new(wrapper));
52 }
53 
54 pub fn dispatch<E: Event>(&self, event: &E) {
55 if let Some(handlers) = self.handlers.get(&TypeId::of::<E>()) {
56 for handler in handlers {
57 handler.call(event.as_any());
58 }
59 }
60 }
61}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Type erasure lets you store heterogeneous generic handlers in one collection behind a uniform trait object.
  2. 2TypeId keys give you runtime type identity so events reach only handlers registered for their exact type.
  3. 3A PhantomData marker preserves the erased type parameter needed later for a safe downcast.

Related explainers

Share this explainer

Here's the card — post it anywhere.

A type-safe event bus in Rust — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code