Rust

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

7 min de leitura

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

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

<h2>Entendendo Async e Await em Rust</h2>

<p>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 <code>async</code> cria uma função que retorna uma <code>Future</code>, um objeto que representa um valor que será computado em algum momento no futuro. O <code>await</code> pausa a execução até que essa <code>Future</code> seja resolvida.</p>

<p>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 <code>Futures</code> são <code>Send</code> e <code>Sync</code> quando apropriado, prevenindo bugs de concorrência em tempo de compilação.</p>

<pre><code class="language-rust">use tokio::time::{sleep, Duration};

#[tokio::main]

async fn main() {

println!(&quot;Iniciando...&quot;);

wait_and_print().await;

println!(&quot;Finalizado!&quot;);

}

async fn wait_and_print() {

sleep(Duration::from_secs(2)).await;

println!(&quot;Acordei após 2 segundos!&quot;);

}</code></pre>

<h2>Futures e o Padrão de Execução</h2>

<p>Uma <code>Future</code> 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 &quot;polled&quot; (consultadas) pelo runtime. Quando você chama uma função <code>async</code>, ela retorna uma <code>Future</code> não iniciada. Apenas quando você a aguarda com <code>.await</code> é que ela começa a execução sob demanda.</p>

<p>O ciclo de vida de uma <code>Future</code> passa por polling repetido até estar pronta (<code>Poll::Ready</code>) ou ainda pendente (<code>Poll::Pending</code>). 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.</p>

<pre><code class="language-rust">use futures::future::join_all;

async fn fetch_user(id: u32) -&gt; String {

println!(&quot;Buscando usuário {}&quot;, id);

tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;

format!(&quot;Usuário {}&quot;, 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!(&quot;Resultados: {:?}&quot;, results);

}</code></pre>

<h2>Trabalhando com Múltiplas Tasks</h2>

<p>Para executar operações realmente concorrentes, use <code>tokio::spawn</code> 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 <code>Future</code> que executa no runtime de Tokio, compartilhando a mesma ou as mesmas threads.</p>

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

<pre><code class="language-rust">#[tokio::main]

async fn main() {

let handle1 = tokio::spawn(async {

tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;

&quot;Task 1 completa&quot;

});

let handle2 = tokio::spawn(async {

tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;

&quot;Task 2 completa&quot;

});

let result1 = handle1.await.unwrap();

let result2 = handle2.await.unwrap();

println!(&quot;{}, {}&quot;, result1, result2);

}</code></pre>

<h2>Tratamento de Erros em Código Assíncrono</h2>

<p>Erros em async funcionam como em código síncrono: <code>Result&lt;T, E&gt;</code> é 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.</p>

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

<pre><code class="language-rust">use tokio::fs;

async fn read_file(path: &amp;str) -&gt; Result&lt;String, std::io::Error&gt; {

fs::read_to_string(path).await

}

#[tokio::main]

async fn main() -&gt; Result&lt;(), Box&lt;dyn std::error::Error&gt;&gt; {

let content = read_file(&quot;arquivo.txt&quot;).await?;

println!(&quot;Conteúdo: {}&quot;, content);

Ok(())

}</code></pre>

<h2>Conclusão</h2>

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

<h2>Referências</h2>

<ul>

<li><a href="https://tokio.rs/" target="_blank" rel="noopener noreferrer">Tokio Official Documentation</a></li>

<li><a href="https://rust-lang.github.io/async-book/" target="_blank" rel="noopener noreferrer">Rust Async Book</a></li>

<li><a href="https://docs.rs/futures/" target="_blank" rel="noopener noreferrer">Futures Documentation on Docs.rs</a></li>

<li><a href="https://aturon.github.io/blog/2016/08/11/futures/" target="_blank" rel="noopener noreferrer">Understanding Rust Futures by Aaron Turon</a></li>

<li><a href="https://medium.com/swlh/rust-async-await-in-practice-e4e37c8e3b5" target="_blank" rel="noopener noreferrer">Async Rust in Practice - Medium</a></li>

</ul>

Comentários

Mais em Rust

O que Todo Dev Deve Saber sobre Argumentos de Linha de Comando com clap em Rust
O que Todo Dev Deve Saber sobre Argumentos de Linha de Comando com clap em Rust

Introdução ao Clap: Por Que Usar? O é a crate mais popular do Rust para parsi...

Boas Práticas de Serialização e Desserialização com Serde em Rust para Times Ágeis
Boas Práticas de Serialização e Desserialização com Serde em Rust para Times Ágeis

Introdução ao Serde: O Padrão de Serialização em Rust Serialização é o proces...

O que Todo Dev Deve Saber sobre Testes Unitários e de Integração em Rust com cargo test
O que Todo Dev Deve Saber sobre Testes Unitários e de Integração em Rust com cargo test

Fundamentos de Testes em Rust Testes em Rust são nativos à linguagem e totalm...