Reactive Programming in Rust: Tool Overview and Code Examples
Reactive programming is a paradigm focused on asynchronous data streams and the propagation of changes. In the Rust ecosystem, it is implemented through powerful libraries such as RxRust (Reactive Extensions), futures-rs, and async-std. These tools allow you to build elegant, fault-tolerant, and high-performance systems, from GUI applications to network services. In this article, we will break down key concepts, show practical examples, and help you choose the right library for your Rust project.
Core Libraries for Reactive Programming in Rust
The most popular libraries for reactive programming in Rust include:
- RxRust — a port of ReactiveX (Rx). Provides Observable, Observer, operators like map, filter, merge, and others. Ideal for event and data stream processing.
- futures-rs — a foundational library for asynchronous computations. Includes Stream, Future, Sink. Underpins many reactive systems.
- async-std — an asynchronous standard library. Provides async versions of file I/O, network operations, and channels for reactive streams.
- tokio — an asynchronous runtime with multi-threading support. Often used in conjunction with futures-rs to build reactive network applications.
Example 1: Using RxRust for Event Handling
RxRust allows you to create Observables from iterators, events, or asynchronous sources. Below is a simple example of generating numbers, filtering, and subscribing to a stream:
use rxrust::prelude::*;
fn main() { // Create an Observable from a range of numbers observable::from_iter(1..10) .filter(|x| x % 2 == 0) // Keep only even numbers .map(|x| x * 10) // Multiply by 10 .subscribe( |x| println!("Received: {}", x), |err| eprintln!("Error: {}", err), || println!("Stream completed"), );}Execution result:
Received: 20Received: 40Received: 60Received: 80Stream completedExample 2: Asynchronous Streams with futures-rs
The futures-rs library provides the Stream trait, which is the asynchronous counterpart of Iterator. The following example demonstrates creating a stream, transforming it, and collecting results:
use futures::stream::{self, StreamExt};
#[tokio::main]async fn main() { let stream = stream::iter(1..=5) .map(|x| x * x) .filter(|x| futures::future::ready(x % 2 == 0));
let results: Vec<i32> = stream.collect().await; println!("Squares of even numbers: {:?}", results);}Output:
Squares of even numbers: [4, 16]Example 3: Reactive Channels in async-std
Channels are the foundation of reactive systems. The async-std library provides multi-producer channels that can be used to pass events between tasks:
use async_std::sync::channel;use async_std::task;
fn main() { let (sender, receiver) = channel::unbounded::<String>();
// Sender task task::spawn(async move { sender.send("Hello".to_string()).await.unwrap(); sender.send("World".to_string()).await.unwrap(); });
// Receiver task (reactive listener) task::block_on(async { while let Some(msg) = receiver.recv().await { println!("Received message: {}", msg); } });}This pattern underlies the architecture of event-driven applications in Rust.
Library Comparison and Recommendations
Also in library
JWT Tokens in Swift: Complete Guide to Working with JSON Web Tokens
Wire for Go: Complete Guide with Examples
Working with S3 and Cloud Storage in TypeScript: A Complete Guide
PDF Generation in Go: Library Overview and Practical Guide
WebSocket libraries for Node.js: ws, Socket.IO and uWebSockets.js