In this lesson, you will learn how to capture the evolution of a system by representing state transitions as a stream of type-safe events using Enums and Traits.
Why Events?
In many systems, simply storing the current state is insufficient. If you want to audit changes or implement undo functionality, you need a history. By modeling domain events, we treat our system as a "ledger" where every change is a discrete, immutable fact that occurred in the past.
The Enum-Trait Synergy
We use an enum to define the vocabulary of possible events, and a trait to define how those events interact with the system state.
trait EventHandler {
fn apply(&mut self, event: SystemEvent);
}
enum SystemEvent {
UserCreated { id: u32, username: String },
PermissionGranted { id: u32, role: String },
UserDeleted { id: u32 },
}
By grouping these variants into a single enum, we gain exhaustive pattern matching. When you write a function to process these events, the Rust compiler forces you to handle every single variant, ensuring that no state transition is left undefined.
Implementation Strategy
The beauty of this approach is that your domain logic becomes a simple state machine. You pass your current state into a function that consumes an event, mutates the state, and potentially logs the event to a persistent store:
struct AppState { users: Vec<String> }
impl EventHandler for AppState {
fn apply(&mut self, event: SystemEvent) {
match event {
SystemEvent::UserCreated { username, .. } => self.users.push(username),
SystemEvent::UserDeleted { id } => { /* logic here */ },
SystemEvent::PermissionGranted { .. } => { /* logic here */ },
}
}
}
This architecture is highly resilient. Because the events are strongly typed, you eliminate the risk of "invalid" system updates. If a developer adds a new event type to the enum, the compiler will immediately highlight every place in your codebase that needs to be updated to support this new change, effectively preventing entire classes of bugs.
Key Takeaway: Modeling system changes as exhaustive enums ensures that all possible state transitions are accounted for at compile time, creating a reliable, auditable trail of your system’s history.