Rust Admin

O que Todo Dev Deve Saber sobre Async e Await em Rust: Introdução à Programação Assíncrona Já leu

Entendendo Async e Await em Rust A programação assíncrona permite que seu programa execute múltiplas operações sem bloquear a thread principal. Ao contrário de linguagens como JavaScript, Rust não oferece async por padrão — você precisa de um runtime como Tokio para executar código assíncrono. A sintaxe cria uma função que retorna uma , um objeto que representa um valor que será computado em algum momento no futuro. O pausa a execução até que essa seja resolvida. Compreender a diferença entre código síncrono (bloqueante) e assíncrono (não-bloqueante) é fundamental. Uma chamada de rede síncrona congela toda a aplicação enquanto aguarda a resposta. Com async/await, você pode atender múltiplos requests simultaneamente usando uma única thread, tornando a aplicação muito mais eficiente. Rust garante segurança com seu sistema de tipos — as são e quando apropriado, prevenindo bugs de concorrência em tempo de compilação. Futures e o Padrão de Execução Uma em Rust é um trait que descreve uma computação assíncrona. Diferente

Entendendo Async e Await em Rust

A programação assíncrona permite que seu programa execute múltiplas operações sem bloquear a thread principal. Ao contrário de linguagens como JavaScript, Rust não oferece async por padrão — você precisa de um runtime como Tokio para executar código assíncrono. A sintaxe async cria uma função que retorna uma Future, um objeto que representa um valor que será computado em algum momento no futuro. O await pausa a execução até que essa Future seja resolvida.

Compreender a diferença entre código síncrono (bloqueante) e assíncrono (não-bloqueante) é fundamental. Uma chamada de rede síncrona congela toda a aplicação enquanto aguarda a resposta. Com async/await, você pode atender múltiplos requests simultaneamente usando uma única thread, tornando a aplicação muito mais eficiente. Rust garante segurança com seu sistema de tipos — as Futures são Send e Sync quando apropriado, prevenindo bugs de concorrência em tempo de compilação.

use tokio::time::{sleep, Duration};

#[tokio::main]
async fn main() {
    println!("Iniciando...");
    wait_and_print().await;
    println!("Finalizado!");
}

async fn wait_and_print() {
    sleep(Duration::from_secs(2)).await;
    println!("Acordei após 2 segundos!");
}

Futures e o Padrão de Execução

Uma Future em Rust é um trait que descreve uma computação assíncrona. Diferente de Promises em JavaScript, Futures em Rust são lazy — não executam até serem "polled" (consultadas) pelo runtime. Quando você chama uma função async, ela retorna uma Future não iniciada. Apenas quando você a aguarda com .await é que ela começa a execução sob demanda.

O ciclo de vida de uma Future passa por polling repetido até estar pronta (Poll::Ready) ou ainda pendente (Poll::Pending). O runtime gerencia esse polling para você. Isso é crucial: você não precisa entender o mecanismo interno de polling para usar async/await, mas entender que Futures são lazy ajuda a escrever código correto.

use futures::future::join_all;

async fn fetch_user(id: u32) -> String {
    println!("Buscando usuário {}", id);
    tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
    format!("Usuário {}", id)
}

#[tokio::main]
async fn main() {
    let futures = vec![
        fetch_user(1),
        fetch_user(2),
        fetch_user(3),
    ];

    let results = join_all(futures).await;
    println!("Resultados: {:?}", results);
}

Trabalhando com Múltiplas Tasks

Para executar operações realmente concorrentes, use tokio::spawn para criar tasks (tarefas) que rodam independentemente. Ao contrário de threads do SO, tasks são muito leves e você pode criar milhares delas. Cada task é uma Future que executa no runtime de Tokio, compartilhando a mesma ou as mesmas threads.

A diferença entre await simples e spawn é importante: await pausa o ponto atual aguardando um resultado sequencial. spawn lança a task imediatamente e continua — você obtém um JoinHandle para aguardar o resultado depois. Para sincronizar múltiplas tasks, use join! do Tokio ou select! para aguardar a primeira que terminar.

#[tokio::main]
async fn main() {
    let handle1 = tokio::spawn(async {
        tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
        "Task 1 completa"
    });

    let handle2 = tokio::spawn(async {
        tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
        "Task 2 completa"
    });

    let result1 = handle1.await.unwrap();
    let result2 = handle2.await.unwrap();

    println!("{}, {}", result1, result2);
}

Tratamento de Erros em Código Assíncrono

Erros em async funcionam como em código síncrono: Result<T, E> é o padrão. A diferença é que você frequentemente lida com erros em múltiplas operações concorrentes. Se uma task falhar, outras continuam — você precisa decidir se quer falhar rápido ou coletar todos os resultados e tratá-los depois.

Para operações críticas onde um erro deve parar tudo, use o operador ? normalmente. Para coletar resultados parciais, itere sobre os JoinHandles e trate cada Result. Sempre use unwrap() com cautela em código de produção — prefira logging e propagação adequada de erros.

use tokio::fs;

async fn read_file(path: &str) -> Result<String, std::io::Error> {
    fs::read_to_string(path).await
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let content = read_file("arquivo.txt").await?;
    println!("Conteúdo: {}", content);
    Ok(())
}

Conclusão

Você aprendeu que async/await em Rust permite escrever código não-bloqueante que se parece com código síncrono, mantendo segurança em tempo de compilação. Futures são lazy e executadas sob polling — não confunda com threads. Use tokio::spawn para paralelismo real, mas lembre-se que await sequencial ainda é útil quando você precisa que uma operação termine antes da próxima começar. Finalmente, Rust força você a tratar erros explicitamente em código assíncrono, o que previne bugs sutis encontrados em outras linguagens. Pratique combinando spawn, join! e select! para dominar padrões de concorrência.

Referências


Artigos relacionados