Introdução à Programação Assíncrona em Rust
Aplicações modernas exigem cada vez mais alto desempenho ao trabalhar com rede, sistema de arquivos ou bancos de dados. A programação assíncrona em Rust permite processar eficientemente milhares de conexões simultâneas sem criar threads para cada tarefa. Ao contrário do multithreading, o código assíncrono usa menos memória e escala melhor.
Rust oferece uma abordagem única para assincronia, baseada em abstrações de custo zero. Isso significa que você paga apenas pelo que usa — sem sobrecarga para funcionalidades não utilizadas. Neste guia, abordaremos os conceitos-chave e escreveremos exemplos práticos que ajudarão você a começar a escrever código assíncrono em Rust hoje mesmo.
Fundamentos de async/await e Futures
O que é Future?
Future é uma trait que representa uma operação assíncrona que pode ser concluída no futuro. Em Rust, Future é uma entidade preguiçosa: ela não faz nada até ser consultada (poll). A ideia principal: em vez de bloquear uma thread, registramos uma tarefa e obtemos o resultado quando ele estiver pronto.
use std::future::Future;use std::pin::Pin;use std::task::{Context, Poll};
struct MyFuture { ready: bool,}
impl Future for MyFuture { type Output = u32;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<u32> { if self.ready { Poll::Ready(42) } else { // Registra o waker para nova consulta cx.waker().wake_by_ref(); Poll::Pending } }}async/await — Açúcar Sintático
A palavra-chave async transforma uma função ou bloco em um Future. await pausa a execução até a conclusão do Future, devolvendo o controle ao runtime.
use tokio::time::{sleep, Duration};
async fn fetch_data() -> String { // Simulação de requisição de rede sleep(Duration::from_secs(2)).await; String::from("Dados obtidos")}
#[tokio::main]async fn main() { let result = fetch_data().await; println!("{}", result);}Ciclos de Vida e Borrow Checker
Uma das características do Rust é o controle rigoroso de empréstimos. No código assíncrono, isso é especialmente importante, pois um Future pode viver mais que os dados emprestados. Use async move para capturar a posse:
async fn process_data(data: Vec<u32>) -> u32 { data.iter().sum()}
#[tokio::main]async fn main() { let numbers = vec![1, 2, 3, 4, 5]; let sum = process_data(numbers).await; println!("Soma: {}", sum);}Trabalho Prático com Tokio
Instalação e Configuração
Tokio é o runtime assíncrono mais popular para Rust. Adicione ao Cargo.toml:
[dependencies]tokio = { version = "1", features = ["full"] }Criando um Servidor TCP Assíncrono
Vamos considerar um servidor echo simples, que aceita conexões e envia de volta os dados recebidos:
use tokio::net::TcpListener;use tokio::io::{AsyncReadExt, AsyncWriteExt};
#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { let listener = TcpListener::bind("127.0.0.1:8080").await?; println!("Servidor iniciado na porta 8080");
loop { let (mut socket, addr) = listener.accept().await?; println!("Nova conexão de: {}", addr);
tokio::spawn(async move { let mut buf = [0; 1024]; loop { let n = socket.read(&mut buf).await.unwrap(); if n == 0 { break; } // Conexão fechada socket.write_all(&buf[..n]).await.unwra