Async/Await Básico em Rust
A programação assíncrona em Rust permite executar muitas tarefas de I/O concorrentemente sem o custo de criar threads do sistema operacional. O runtime tokio é o mais popular e robusto do ecossistema.
Dependências
Cargo.toml:
[dependencies]
tokio = { version = "1", features = ["full"] }
Primeira função assíncrona
Uma função async retorna uma Future que precisa ser executada por um runtime:
use std::time::Duration;
use tokio::time::sleep;
// Funções async retornam implicitamente uma Future
async fn saudacao(nome: &str) -> String {
// .await pausa esta função até o sleep completar
sleep(Duration::from_millis(100)).await;
format!("Olá, {}!", nome)
}
async fn calcular(x: i32, y: i32) -> i32 {
sleep(Duration::from_millis(50)).await;
x + y
}
// #[tokio::main] cria o runtime e executa a função async
#[tokio::main]
async fn main() {
println!("Início");
let msg = saudacao("Rust").await;
println!("{}", msg);
let resultado = calcular(20, 22).await;
println!("20 + 22 = {}", resultado);
println!("Fim");
}
Saída:
Início
Olá, Rust!
20 + 22 = 42
Fim
Executar tarefas em paralelo com join!
O macro join! executa múltiplas futures concorrentemente:
use std::time::{Duration, Instant};
use tokio::time::sleep;
async fn buscar_usuario() -> String {
sleep(Duration::from_millis(200)).await;
"Maria Silva".to_string()
}
async fn buscar_pedidos() -> Vec<String> {
sleep(Duration::from_millis(300)).await;
vec!["Pedido #1".to_string(), "Pedido #2".to_string()]
}
async fn buscar_saldo() -> f64 {
sleep(Duration::from_millis(150)).await;
1500.50
}
#[tokio::main]
async fn main() {
// Sequencial — cada await bloqueia o próximo
let inicio = Instant::now();
let _usuario = buscar_usuario().await;
let _pedidos = buscar_pedidos().await;
let _saldo = buscar_saldo().await;
println!("Sequencial: {:?}", inicio.elapsed());
// Paralelo com join! — todas executam ao mesmo tempo
let inicio = Instant::now();
let (usuario, pedidos, saldo) = tokio::join!(
buscar_usuario(),
buscar_pedidos(),
buscar_saldo()
);
println!("Paralelo: {:?}", inicio.elapsed());
println!("\nUsuário: {}", usuario);
println!("Pedidos: {:?}", pedidos);
println!("Saldo: R${:.2}", saldo);
}
Saída:
Sequencial: 650ms
Paralelo: 300ms
Usuário: Maria Silva
Pedidos: ["Pedido #1", "Pedido #2"]
Saldo: R$1500.50
Spawn de tarefas com tokio::spawn
Lance tarefas assíncronas independentes:
use tokio::time::{sleep, Duration};
async fn processar_item(id: u32) -> String {
sleep(Duration::from_millis(100 * id as u64)).await;
format!("Item {} processado", id)
}
#[tokio::main]
async fn main() {
// Criar várias tarefas assíncronas
let mut handles = Vec::new();
for id in 1..=5 {
// tokio::spawn lança uma tarefa no runtime
let handle = tokio::spawn(async move {
let resultado = processar_item(id).await;
println!(" {}", resultado);
resultado
});
handles.push(handle);
}
// Coletar resultados
let mut resultados = Vec::new();
for handle in handles {
let resultado = handle.await.unwrap();
resultados.push(resultado);
}
println!("\nTodos processados: {} tarefas", resultados.len());
}
Saída:
Item 1 processado
Item 2 processado
Item 3 processado
Item 4 processado
Item 5 processado
Todos processados: 5 tarefas
select! — responder à primeira future
O macro select! executa múltiplas futures e age na primeira que completar:
use tokio::time::{sleep, Duration};
async fn servidor_principal() -> String {
sleep(Duration::from_millis(300)).await;
"Resposta do servidor principal".to_string()
}
async fn servidor_backup() -> String {
sleep(Duration::from_millis(200)).await;
"Resposta do servidor backup".to_string()
}
async fn timeout_seguranca() {
sleep(Duration::from_millis(500)).await;
}
#[tokio::main]
async fn main() {
// Pegar a resposta mais rápida
let resultado = tokio::select! {
resp = servidor_principal() => {
println!("Principal respondeu primeiro");
resp
}
resp = servidor_backup() => {
println!("Backup respondeu primeiro");
resp
}
_ = timeout_seguranca() => {
println!("Timeout!");
"Nenhum servidor respondeu".to_string()
}
};
println!("Resultado: {}", resultado);
}
Saída:
Backup respondeu primeiro
Resultado: Resposta do servidor backup
Timeout em operações assíncronas
Adicione limites de tempo às suas operações:
use std::time::Duration;
use tokio::time::{sleep, timeout};
async fn operacao_lenta() -> String {
sleep(Duration::from_secs(5)).await;
"Concluído!".to_string()
}
async fn operacao_rapida() -> String {
sleep(Duration::from_millis(100)).await;
"Rápido!".to_string()
}
#[tokio::main]
async fn main() {
// Operação com timeout
match timeout(Duration::from_secs(1), operacao_lenta()).await {
Ok(resultado) => println!("Sucesso: {}", resultado),
Err(_) => println!("Timeout! Operação demorou mais de 1 segundo"),
}
// Operação que completa a tempo
match timeout(Duration::from_secs(1), operacao_rapida()).await {
Ok(resultado) => println!("Sucesso: {}", resultado),
Err(_) => println!("Timeout!"),
}
}
Saída:
Timeout! Operação demorou mais de 1 segundo
Sucesso: Rápido!
Canais assíncronos com mpsc
Comunique entre tarefas assíncronas:
use tokio::sync::mpsc;
use tokio::time::{sleep, Duration};
#[derive(Debug)]
struct Mensagem {
remetente: String,
conteudo: String,
}
#[tokio::main]
async fn main() {
let (tx, mut rx) = mpsc::channel::<Mensagem>(32); // buffer de 32
// Produtores
for i in 1..=3 {
let tx = tx.clone();
tokio::spawn(async move {
for j in 1..=2 {
let msg = Mensagem {
remetente: format!("Produtor {}", i),
conteudo: format!("Mensagem {}", j),
};
tx.send(msg).await.unwrap();
sleep(Duration::from_millis(50 * i as u64)).await;
}
});
}
// Dropar transmissor original
drop(tx);
// Consumidor
let mut total = 0;
while let Some(msg) = rx.recv().await {
println!("[{}] {}", msg.remetente, msg.conteudo);
total += 1;
}
println!("\nTotal recebido: {} mensagens", total);
}
Saída (ordem pode variar):
[Produtor 1] Mensagem 1
[Produtor 2] Mensagem 1
[Produtor 3] Mensagem 1
[Produtor 1] Mensagem 2
[Produtor 2] Mensagem 2
[Produtor 3] Mensagem 2
Total recebido: 6 mensagens
Quando usar threads vs async
| Cenário | Melhor opção |
|---|---|
| Muitas conexões de rede | async/await (tokio) |
| Cálculos pesados (CPU) | std::thread ou rayon |
| Leitura de muitos arquivos | async/await (tokio) |
| Processamento de imagens | rayon (thread pool) |
| Servidor web | async/await (axum/tokio) |
| Parser de dados grandes | rayon para paralelismo de dados |
Veja também
- Executar Threads — concorrência com threads do SO
- Fazer Requisição HTTP — uso prático de async com reqwest
- Criar Servidor HTTP — servidor web assíncrono com axum
- Conectar ao PostgreSQL — queries assíncronas ao banco
- Ler Arquivo em Rust — I/O de arquivo (sync vs async)
- Documentação da crate: tokio