Event Bus and Observer Pattern in Rust: Implementing Event-Driven Architecture

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Event Bus and Observer Pattern in Rust: Building Flexible Event-Driven Architecture



In modern software development, there is often a need for loose coupling between components. The Observer pattern and its practical implementation — Event Bus — allow organizing efficient interaction between modules without direct dependencies. Rust, with its strict type system and emphasis on safety, provides excellent capabilities for implementing this pattern. In this article, we will explore how to build your own Event Bus in Rust using the language's built-in features and popular crates.



What is Event Bus and the Observer Pattern?



Observer Pattern defines a one-to-many dependency between objects: when one object (publisher) changes its state, all dependent objects (subscribers) are automatically notified. Event Bus is a centralized bus that manages subscriptions and event dispatching, allowing any component to publish events and subscribe to them.



Key advantages:

  • Loose coupling: publishers and subscribers do not know about each other directly
  • Scalability: easily add new subscribers without modifying existing code
  • Testability: components can be tested in isolation by emulating events
  • Asynchronicity: events can be processed in the background


Basic Implementation of Event Bus in Rust



Let's start with a simple synchronous implementation using standard Rust tools. It will be based on a HashMap where the key is the event type and the value is a list of handlers.



use std::collections::HashMap;use std::any::{Any, TypeId};use std::marker::PhantomData;

// Event type with a unique identifierpub trait Event: Any + Clone + Send + 'static { fn event_type(&self) -> TypeId { TypeId::of::<Self>() }}

// Event handlertrait EventHandler<E: Event>: Send + 'static { fn handle(&self, event: &E);}

// The event bus itselfpub struct EventBus { handlers: HashMap<TypeId, Vec<Box<dyn Any>>>,}

impl EventBus { pub fn new() -> Self { EventBus { handlers: HashMap::new(), } }

pub fn subscribe<E: Event, H: EventHandler<E>>(&mut self, handler: H) { let type_id = TypeId::of::<E>(); let boxed_handler = Box::new(handler) as Box<dyn Any>; self.handlers.entry(type_id).or_insert(Vec::new()).push(boxed_handler); }

pub fn publish<E: Event>(&self, event: &E) { let type_id = event.event_type(); if let Some(handlers) = self.handlers.get(&type_id) { for handler in handlers { if let Some(typed_handler) = handler.downcast_ref::<Box<dyn EventHandler<E>>>() { typed_handler.handle(event); } } } }}


Usage Example



// Define events#[derive(Clone)]struct UserLoggedIn {    user_id: u32,    username: String,}

impl Event for UserLoggedIn {}

#[derive(Clone)]struct DataUpdated { table: String, record_id: u64,}

impl Event for DataUpdated {}

// Define handlersstruct Logger;impl EventHandler<UserLoggedIn> for Logger { fn handle(&self, event: &UserLoggedIn) { println!("[LOG] User {} (ID: {}) logged in", event.username, event.user_id); }}

struct EmailNotifier;impl EventHandler<UserLoggedIn> for EmailNotifier { fn handle(&self, event: &UserLoggedIn) { println!("[EMAIL] Sending welcome email to {}", event.username); }}

fn main() { le

Recommendations