Reactive Programming in Rust: Libraries, Frameworks, and Practical Examples

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

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 completed


Example 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

Recommendations