Event-driven Architecture in Rust: Real Examples and Practice

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

When Events Control the Code: Event-driven in Rust

\n\n

Imagine that your code is an orchestra conductor. In classical synchronous programming, the conductor decides when each note should play. In event-driven architecture, the conductor simply reacts to the fact that the musicians have already started playing. Sounds paradoxical? Only at first glance.

\n\n

Event-driven architecture is an approach where the program's execution flow is determined by events: a mouse click, receiving an HTTP request, completing I/O, a timer. Rust, with its ownership system and zero-cost abstractions, is an ideal foundation for such architecture. Especially if you come from Python, where the event loop (e.g., in asyncio) works but often runs into the GIL and the overhead of dynamic typing.

\n\n

In this article, we won't just talk about theory. We will write real code, break down event-driven architecture in Rust using popular crates, and show how it can speed up your applications significantly.

\n\n

Why Rust, Not Python, for Events?

\n\n

Python is an excellent language for prototyping. But when you need real-time event processing with thousands of concurrent connections, Python starts to slow down. Rust, on the other hand, provides:

\n\n
    \n
  • Zero-cost abstractions — async/await adds no overhead if you don't use its features
  • \n
  • No garbage collector — no sudden pauses during event processing
  • \n
  • A type system that catches errors at compile time
  • \n
  • True parallelism — not cooperative multitasking, but real threads without a GIL
  • \n
\n\n

If you write event-driven applications in Rust, you get control over every byte of memory and every CPU cycle. And this isn't marketing — it's a fact.

\n\n

Basics: Event Loop and Event Processing

\n\n

At the heart of any event-driven system lies the event loop. This is an infinite loop that:

\n\n
    \n
  1. Waits for a new event to appear
  2. \n
  3. Retrieves it from the queue
  4. \n
  5. Passes it to the handler
  6. \n
  7. Repeats
  8. \n
\n\n

In Rust, the standard event loop is provided by the tokio crate. Let's write a simple example:

\n\n
use tokio::sync::mpsc;\nuse tokio::time::{sleep, Duration};\n\n#[tokio::main]\nasync fn main() {\n    // Create a channel for passing events\n    let (tx, mut rx) = mpsc::channel::<String>(32);\n\n    // Producer: generates events\n    tokio::spawn(async move {\n        for i in 0..5 {\n            let event = format!("Event №{}", i);\n            tx.send(event).await.unwrap();\n            sleep(Duration::from_millis(500)).await;\n        }\n    });\n\n    // Consumer: processes events\n    while let Some(event) = rx.recv().await {\n        println!("Processed: {}", event);\n        // Your business logic can go here\n    }\n}
\n\n

This code demonstrates the basic event-driven programming pattern in Rust: one component generates events, another processes them. The mpsc (multi-producer, single-consumer) channel ensures safe data transfer between tasks.

\n\n

Advanced Example: Order Processing System

\n\n

Let's make the task more complex. Imagine we are writing a backend for an online store. When a new order arrives, we need to:

\n\n
    \n
  • Check product availability
  • \n
  • Reserve it
  • \n
  • Send a notification
  • \n
  • Update statistics
  • \n
\n\n

The classic approach is to do all this synchronously. The event-driven approach is to break it down into events:

\n\n
use tokio::sync::broadcast;\nuse tokio::time::sleep;\nuse std::time::Duration;\n\n#[derive(Debug, Clone)]\nenum OrderEvent {\n    NewOrder { id: u32, items: Vec<String>                

Blogs

Book Recommendations