E0728: await Usado Fora de Função Async no Rust

Como resolver o erro E0728 do Rust: .await usado fora de contexto async. Entenda quando e onde usar async/await e como estruturar código assíncrono corretamente.

E0728: await Usado Fora de Função Async

O erro E0728 ocorre quando você usa .await em um contexto que não é async — ou seja, fora de uma função async fn, um bloco async {} ou uma closure async. O .await só pode ser usado dentro de contextos assíncronos.

A Mensagem de Erro

error[E0728]: `await` is only allowed inside `async` functions and blocks
 --> src/main.rs:5:29
  |
4 | fn buscar_dados() -> String {
  |    ------------ this is not `async`
5 |     let resposta = reqwest::get("https://api.exemplo.com").await;
  |                                                           ^^^^^^ only allowed inside `async` functions and blocks

Variação em closure não-async:

error[E0728]: `await` is only allowed inside `async` functions and blocks
 --> src/main.rs:3:38
  |
3 |     let processar = |url| { fetch(url).await };
  |                     -----              ^^^^^^ only allowed inside `async` functions and blocks
  |                     |
  |                     this is not `async`

O Que Significa

No Rust, async/await é um mecanismo de programação assíncrona que permite escrever código não-bloqueante de forma legível. O .await pausa a execução da função atual até que o future (operação assíncrona) seja concluído.

Porém, .await só funciona dentro de um contexto async porque:

  1. Funções async são transformadas em máquinas de estado pelo compilador
  2. O .await marca os pontos de pausa nessa máquina de estado
  3. Funções síncronas normais não têm essa infraestrutura

Sem o contexto async, o compilador não sabe como gerar a máquina de estado necessária para pausar e retomar a execução.

Código com Erro

Função síncrona tentando usar await

// ERRO: função não é async
fn buscar_dados() -> String {
    let corpo = reqwest::get("https://api.exemplo.com")
        .await  // ERRO: não está em contexto async
        .unwrap()
        .text()
        .await  // ERRO: mesmo problema
        .unwrap();
    corpo
}

main() sem macro async

// ERRO: main normal não é async
fn main() {
    let dados = buscar_dados().await;  // ERRO
    println!("{}", dados);
}

async fn buscar_dados() -> String {
    "dados".to_string()
}

Closure não-async

async fn processar_urls(urls: Vec<String>) {
    let resultados: Vec<_> = urls.iter().map(|url| {
        // ERRO: closure não é async
        reqwest::get(url).await
    }).collect();
}

Como Resolver

Solução 1: Tornar a Função Async

Adicione async à declaração da função:

// CORRETO: função async
async fn buscar_dados() -> String {
    let corpo = reqwest::get("https://api.exemplo.com")
        .await
        .unwrap()
        .text()
        .await
        .unwrap();
    corpo
}

Lembre-se: uma função async fn retorna um Future, não o valor diretamente. O chamador precisa usar .await (que também precisa estar em contexto async).

Solução 2: Usar Runtime Async para main()

Com tokio:

#[tokio::main]
async fn main() {
    let dados = buscar_dados().await;
    println!("{}", dados);
}

async fn buscar_dados() -> String {
    "dados".to_string()
}

Certifique-se de ter as features corretas:

[dependencies]
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }

Com async-std:

#[async_std::main]
async fn main() {
    let dados = buscar_dados().await;
    println!("{}", dados);
}

Solução 3: Usar Bloco async {} dentro de Contexto Síncrono

Para executar código async em contexto síncrono, use block_on:

fn main() {
    let rt = tokio::runtime::Runtime::new().unwrap();

    let resultado = rt.block_on(async {
        buscar_dados().await
    });

    println!("{}", resultado);
}

async fn buscar_dados() -> String {
    "dados do servidor".to_string()
}

Ou com a versão simplificada:

fn funcao_sincrona() -> String {
    // Cria runtime temporário para executar código async
    tokio::runtime::Runtime::new()
        .unwrap()
        .block_on(buscar_dados())
}

async fn buscar_dados() -> String {
    "dados".to_string()
}

Cuidado: Não use block_on dentro de um contexto que já está em um runtime async — isso causa deadlock.

Solução 4: Closures Async

Para closures, use a sintaxe async:

async fn processar_urls(urls: Vec<String>) {
    // Use futures::future::join_all para processar em paralelo
    let futures = urls.into_iter().map(|url| {
        async move {  // Closure async
            let resp = reqwest::get(&url).await.unwrap();
            resp.text().await.unwrap()
        }
    });

    let resultados: Vec<String> = futures::future::join_all(futures).await;
    println!("{:?}", resultados);
}

Ou processe sequencialmente com um loop:

async fn processar_sequencial(urls: Vec<String>) {
    for url in &urls {
        let resp = reqwest::get(url).await.unwrap();
        let corpo = resp.text().await.unwrap();
        println!("URL: {} -> {} bytes", url, corpo.len());
    }
}

Solução 5: Traits com Métodos Async

Para traits com métodos async, use o crate async-trait ou a sintaxe nativa (estabilizada no Rust 1.75+):

// Com async-trait (compatível com versões anteriores)
use async_trait::async_trait;

#[async_trait]
trait Repositorio {
    async fn buscar(&self, id: u64) -> Option<String>;
    async fn salvar(&self, id: u64, valor: String) -> Result<(), String>;
}

struct BancoDeDados;

#[async_trait]
impl Repositorio for BancoDeDados {
    async fn buscar(&self, id: u64) -> Option<String> {
        Some(format!("Item {}", id))
    }

    async fn salvar(&self, id: u64, valor: String) -> Result<(), String> {
        println!("Salvando {} = {}", id, valor);
        Ok(())
    }
}

Resumo: Onde .await Funciona

ContextoPode usar .await?
async fnSim
async { ... } blocoSim
async move { ... }Sim
fn normalNao
Closure || { ... }Nao
async || { ... }Sim
#[tokio::main] async fn main()Sim
fn main() sem macroNao

Veja Também