Course contentsShow
← Rust
Lesson 742 of 1,9106. Generics and Static DispatchFree lesson

Generic Event Systems

Create decoupled event dispatchers where the event payload is determined by the listener type.

In this lesson, you will learn how to build a decoupled event dispatcher where the event payload is determined entirely by the listener's type.

Decoupling with Traits

When building a system where different parts of an application need to communicate without knowing about each other, we use a generic "Event" pattern. Instead of a hardcoded dispatcher, we define a generic trait that represents a listener capable of handling a specific type of event.

Consider an EventListener<E> trait:

trait EventListener<E> {
    fn on_event(&self, event: &E);
}

By making the trait generic over E, we allow a single struct to implement multiple event listeners. A Logger could listen to both UserLogin and SystemError events without those two event types ever needing to know about each other.

The Dispatcher

The dispatcher acts as the middleman. It stores a collection of listeners. However, because we want the dispatcher to be generic, it shouldn't care about the specific logic inside the listeners; it only needs to know that they implement EventListener<E>.

struct Dispatcher<E> {
    listeners: Vec<Box<dyn EventListener<E>>>,
}

impl<E> Dispatcher<E> {
    fn dispatch(&self, event: E) {
        for listener in &self.listeners {
            listener.on_event(&event);
        }
    }
}

Why This Matters

This design provides two main benefits:

  1. Type Safety: The compiler ensures that the Dispatcher<UserLogin> only accepts UserLogin structs. You cannot accidentally pass a SystemError to a listener that isn't built to handle it.
  2. Decoupling: The Dispatcher does not need to be modified when you add a new event type or a new listener implementation. You simply create a new Dispatcher<NewEvent> and register your types. Because the payload type is determined by the E generic, the system remains strictly typed while remaining completely flexible regarding what data is being transmitted.

This approach keeps your business logic isolated from your communication infrastructure, which is the hallmark of a robust, maintainable architecture.

Key Takeaway: By using generic trait bounds, you can create a type-safe event system that delegates payload handling to specific listener implementations, ensuring your dispatchers stay decoupled from the data they carry.