Event Bus and Observer Pattern in Rust: Implementation and Examples
Event Bus is an architectural pattern that implements the publish-subscribe principle. In Rust, this pattern is especially useful for building loosely coupled systems where components communicate through events without knowing about each other directly. In this article, we will analyze how to create your own Event Bus using the Observer pattern and consider ready-made solutions from the Rust ecosystem.
Observer Pattern: Basics
The Observer pattern defines a one-to-many relationship between objects. When one object (publisher) changes its state, all dependent objects (subscribers) are automatically notified. In Rust, this is often implemented through traits and dynamic dispatch.
Basic Observer Implementation in Rust
use std::collections::HashMap;
// Trait for observertrait Observer { fn update(&self, event: &str);}
// Publisher structurestruct EventManager { observers: Vec<Box<dyn Observer>>,}
impl EventManager { fn new() -> Self { EventManager { observers: Vec::new(), } }
fn subscribe(&mut self, observer: Box<dyn Observer>) { self.observers.push(observer); }
fn notify(&self, event: &str) { for observer in &self.observers { observer.update(event); } }}
// Concrete observerstruct Logger;
impl Observer for Logger { fn update(&self, event: &str) { println!("[Logger] Event received: {}", event); }}
fn main() { let mut manager = EventManager::new(); manager.subscribe(Box::new(Logger)); manager.notify("user.login");}Event Bus: Extended Version of Observer
Event Bus is a centralized bus that routes events by type. Unlike a simple Observer, it supports different event types and subscription filtering.
Event Bus Implementation with Event Types
use std::any::Any;use std::collections::HashMap;use std::sync::{Arc, Mutex};
// Event type#[derive(Clone, Debug)]enum EventType { UserLogin, UserLogout, DataUpdate,}
// Event structure#[derive(Clone, Debug)]struct Event { event_type: EventType, payload: String,}
// Event handlertype Handler = Arc<dyn Fn(Event) + Send + Sync>;
// Event busstruct EventBus { handlers: HashMap<EventType, Vec<Handler>>,}
impl EventBus { fn new() -> Self { EventBus { handlers: HashMap::new(), } }
fn subscribe<F>(&mut self, event_type: EventType, handler: F) where F: Fn(Event) + 'static + Send + Sync, { self.handlers .entry(event_type) .or_insert_with(Vec::new) .push(Arc::new(handler)); }
fn publish(&self, event: Event) { if let Some(handlers) = self.handlers.get(&event.event_type) { for handler in handlers { handler(event.clone()); } } }}
fn main() { let mut bus = EventBus::new();
bus.subscribe(EventType::UserLogin, |event| { println!("User logged in: {}", event.payload); });
bus.subscribe(EventType::UserLogin, |event| { // Another handler println!("Login recorded: {}", event.payload); });
bus.publish(Event { event_type: EventType::UserLogin, payload: "user123".to_string(), });}Asynchronous Event Bus with Tokio
In real applications, asynchronous event processing is often required. The tokio library with its broadcast channels is perfect for this.
Using tokio::sync::broadcast
use tokio::sync::broadcast;
#[tokio::main]async fn m