Programação Reativa em Rust: de objetos observáveis a fluxos assíncronos
Programação reativa é um paradigma focado no processamento de fluxos de dados assíncronos e na propagação de mudanças. No ecossistema Rust, ela é implementada através de uma combinação de um poderoso sistema de tipos, gerenciamento seguro de memória e primitivas assíncronas modernas. Neste artigo, exploraremos as principais bibliotecas, padrões e exemplos práticos de programação reativa em Rust.
Principais bibliotecas para programação reativa em Rust
Não existe um "padrão" único de programação reativa em Rust, mas existem várias bibliotecas maduras que cobrem diferentes níveis de abstração:
- RxRust — uma adaptação da popular ReactiveX (Rx) para Rust. Fornece Observable, Observer, Scheduler e um amplo conjunto de operadores (map, filter, flat_map, merge, etc.).
- futures-rs — biblioteca fundamental para trabalhar com computações assíncronas. O trait Stream é a base dos fluxos reativos.
- async-std e tokio — runtimes assíncronos que fornecem canais (channels), temporizadores e outros primitivos para cadeias reativas.
- frunk — biblioteca para programação funcional, incluindo padrões reativos como Lens e State.
Exemplo 1: Observable simples com RxRust
RxRust implementa o padrão "observador": um Observable gera eventos e um Observer se inscreve neles.
// Cargo.toml: rxrust = "1.0"use rxrust::prelude::*;
fn main() { let observable = observable::from_iter(vec![1, 2, 3, 4, 5]);
observable .filter(|x| x % 2 == 0) .map(|x| x * 10) .subscribe(|x| println!("Recebido: {}", x));}Neste exemplo, um fluxo é criado a partir dos números 1..5, os pares são filtrados, multiplicados por 10 e exibidos. A cadeia de operadores é executada de forma lazy — somente após a chamada de subscribe.
Exemplo 2: Fluxos assíncronos com futures-rs
A biblioteca futures-rs fornece o trait Stream, que é o equivalente assíncrono de Iterator. Os combinadores de StreamExt permitem construir cadeias reativas.
// Cargo.toml: futures = "0.3"use futures::stream::{self, StreamExt};
#[tokio::main]async fn main() { let stream = stream::iter(0..10) .filter(|x| futures::future::ready(x % 2 == 0)) .map(|x| x * 10) .take(3);
let result: Vec<i32> = stream.collect().await; println!("Resultado: {:?}", result); // [0, 20, 40]}Aqui, filter aceita uma função assíncrona, permitindo incorporar chamadas a APIs externas ou bancos de dados dentro da cadeia. take(3) limita a quantidade de elementos — este é um exemplo de "contrapressão" (backpressure).
Exemplo 3: Canais reativos com async-std
Canais são uma forma clássica de implementar interações reativas entre componentes. O async-std fornece canais multiprodutores e multiconsumidores.
// Cargo.toml: async-std = "1.12"use async_std::channel;use async_std::task;
#[async_std::main]async fn main() { let (sender, receiver) = channel::unbounded();
// Produtor task::spawn(async move { for i in 0..5 { sender.send(i).await.unwrap(); task::sleep(std::time::Duration::from_millis(100)).await; } });
// Consumidor (reativo) while let Some(msg) = receiver.recv().await { println!("Recebido: {}", msg); if msg == 4 { break; } }}Canais são ideais para implementar um Event Bus, barramento de eventos ou sistemas reativos simples com múltiplas fontes de dados.
Padrões avançados: combinando fluxos
Também na biblioteca
Testes em JavaScript (Node.js): frameworks e ferramentas
Trabalhando com Markdown em Kotlin: bibliotecas, parsing e geração
Autorização e Autenticação em C# (.NET): Guia Completo
Eloquent ORM para PHP: guia completo com exemplos
Geolocalização e Mapas em PHP: Visão Geral de Bibliotecas e Ferramentas