Rust

Como Usar O Operador ? em Rust: Propagação de Erros Elegante em Produção

8 min de leitura

Como Usar O Operador ? em Rust: Propagação de Erros Elegante em Produção

O Operador ? em Rust: Propagação de Erros Elegante O que é o Operador ? O operador é um atalho poderoso em Rust para propagação de erros. Quando usado em uma função que retorna ou , ele funciona como um "desempacotador inteligente": se o valor é um sucesso ( ou ), ele extrai o conteúdo; se for um erro ( ou ), ele imediatamente retorna esse erro da função atual. Isso elimina a necessidade de escrever condicionais verbosos com ou , tornando o código mais legível e seguro. O foi introduzido no Rust 1.13 como resposta à necessidade de tratamento de erros mais ergonômico. Em vez de usar (macro anterior), você escreve código que flui naturalmente enquanto mantém a segurança de tipos que Rust oferece. Sintaxe e Casos de Uso O operador pode ser usado em qualquer função que retorna ou . A sintaxe é simples: coloque imediatamente após a expressão que você quer desempacotar. Você não pode usar em

<h2>O Operador ? em Rust: Propagação de Erros Elegante</h2>

<h3>O que é o Operador ?</h3>

<p>O operador <code>?</code> é um atalho poderoso em Rust para propagação de erros. Quando usado em uma função que retorna <code>Result&lt;T, E&gt;</code> ou <code>Option&lt;T&gt;</code>, ele funciona como um &quot;desempacotador inteligente&quot;: se o valor é um sucesso (<code>Ok</code> ou <code>Some</code>), ele extrai o conteúdo; se for um erro (<code>Err</code> ou <code>None</code>), ele imediatamente retorna esse erro da função atual. Isso elimina a necessidade de escrever condicionais verbosos com <code>match</code> ou <code>unwrap</code>, tornando o código mais legível e seguro.</p>

<p>O <code>?</code> foi introduzido no Rust 1.13 como resposta à necessidade de tratamento de erros mais ergonômico. Em vez de usar <code>try!</code> (macro anterior), você escreve código que flui naturalmente enquanto mantém a segurança de tipos que Rust oferece.</p>

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

use std::io;

// Sem o operador ?

fn ler_arquivo_verboso(caminho: &amp;str) -&gt; Result&lt;String, io::Error&gt; {

let conteudo = match fs::read_to_string(caminho) {

Ok(dados) =&gt; dados,

Err(e) =&gt; return Err(e),

};

Ok(conteudo)

}

// Com o operador ?

fn ler_arquivo(caminho: &amp;str) -&gt; Result&lt;String, io::Error&gt; {

let conteudo = fs::read_to_string(caminho)?;

Ok(conteudo)

}</code></pre>

<h3>Sintaxe e Casos de Uso</h3>

<p>O operador <code>?</code> pode ser usado em qualquer função que retorna <code>Result&lt;T, E&gt;</code> ou <code>Option&lt;T&gt;</code>. A sintaxe é simples: coloque <code>?</code> imediatamente após a expressão que você quer desempacotar. Você não pode usar <code>?</code> em funções que retornam <code>()</code> (void) — a função precisa retornar um tipo que implemente o trait <code>Try</code>.</p>

<p>Um detalhe importante: quando você usa <code>?</code> com <code>Result</code>, o erro é automaticamente convertido usando o trait <code>From</code>. Isso significa que se sua função retorna <code>Result&lt;T, MinhaError&gt;</code>, mas uma operação retorna <code>Result&lt;T, OutraError&gt;</code>, Rust tentará converter <code>OutraError</code> em <code>MinhaError</code> usando <code>From::from()</code>. Isso é extraordinariamente conveniente para consolidar múltiplos tipos de erro.</p>

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

use std::num::ParseIntError;

#[derive(Debug)]

enum MinhaError {

IoError(std::io::Error),

ParseError(ParseIntError),

}

impl From&lt;std::io::Error&gt; for MinhaError {

fn from(err: std::io::Error) -&gt; Self {

MinhaError::IoError(err)

}

}

impl From&lt;ParseIntError&gt; for MinhaError {

fn from(err: ParseIntError) -&gt; Self {

MinhaError::ParseError(err)

}

}

fn processar_arquivo(caminho: &amp;str) -&gt; Result&lt;i32, MinhaError&gt; {

let conteudo = fs::read_to_string(caminho)?; // Pode retornar IoError

let numero = conteudo.trim().parse::&lt;i32&gt;()?; // Pode retornar ParseIntError

Ok(numero)

}</code></pre>

<h3>Operador ? com Option</h3>

<p>Além de <code>Result</code>, o operador <code>?</code> também funciona com <code>Option&lt;T&gt;</code>. Quando você usa <code>?</code> em um <code>Option</code>, se for <code>None</code>, a função retorna <code>None</code> imediatamente. Isso é útil para cadeias de operações opcionais onde qualquer <code>None</code> significa que não há resultado válido.</p>

<p>A combinação de <code>?</code> com métodos que retornam <code>Option</code> torna código que busca valores aninhados extremamente limpo. Por exemplo, navegar por estruturas de dados aninhadas que podem não existir fica muito mais expressivo do que usar múltiplos <code>if let</code> ou <code>match</code>.</p>

<pre><code class="language-rust">fn extrair_idade(dados: Option&lt;(&amp;str, u32)&gt;) -&gt; Option&lt;u32&gt; {

let (_nome, idade) = dados?;

let idade_dobrada = idade.checked_mul(2)?; // Retorna None se overflow

Some(idade_dobrada)

}

fn main() {

println!(&quot;{:?}&quot;, extrair_idade(Some((&quot;Alice&quot;, 25)))); // Some(50)

println!(&quot;{:?}&quot;, extrair_idade(None)); // None

}</code></pre>

<h3>Boas Práticas e Limitações</h3>

<p>O operador <code>?</code> não é uma bala de prata. Há situações onde <code>match</code> ou <code>if let</code> são mais apropriados: quando você precisa fazer algo específico com o erro (logging, recuperação parcial) antes de propagar, ou quando precisa decidir entre múltiplos caminhos baseado no tipo exato do erro. Use <code>?</code> para o caminho &quot;feliz&quot; direto; use <code>match</code> para lógica condicional complexa.</p>

<p>Uma limitação prática: você não pode usar <code>?</code> em closures que retornam tipos simples. Se um closure retorna <code>()</code>, você não pode usar <code>?</code> dentro dele. Para isso, use <code>.map_err()</code> ou converta a closure em uma função separada. Além disso, em <code>main()</code>, você não pode usar <code>?</code> diretamente — a função <code>main</code> retorna <code>()</code>. Porém, em Rust moderno, você pode fazer <code>fn main() -&gt; Result&lt;(), Box&lt;dyn std::error::Error&gt;&gt;</code> para permitir <code>?</code>.</p>

<pre><code class="language-rust">fn main() -&gt; Result&lt;(), Box&lt;dyn std::error::Error&gt;&gt; {

let dados = std::fs::read_to_string(&quot;config.txt&quot;)?;

let numero: i32 = dados.trim().parse()?;

println!(&quot;Valor: {}&quot;, numero);

Ok(())

}</code></pre>

<h2>Conclusão</h2>

<p>O operador <code>?</code> em Rust é um exemplo brilhante de design de linguagem: fornece conveniência sem sacrificar segurança. Ele elimina boilerplate verboso enquanto força o programador a estar ciente de que erros podem ocorrer. Aprenda a usá-lo naturalmente para seu código fluir entre operações que podem falhar, combine-o com traits <code>From</code> customizados para consolidar tipos de erro, e reserve <code>match</code> para lógica que exige decisões explícitas sobre cada tipo de falha.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://doc.rust-lang.org/book/ch09-00-error-handling.html" target="_blank" rel="noopener noreferrer">The Rust Book - Error Handling</a></li>

<li><a href="https://doc.rust-lang.org/rust-by-example/error/result.html" target="_blank" rel="noopener noreferrer">Rust by Example - Result</a></li>

<li><a href="https://doc.rust-lang.org/reference/expressions/operator-expr.html#the-question-mark-operator" target="_blank" rel="noopener noreferrer">Official Rust Documentation - The ? operator</a></li>

<li><a href="https://www.infoq.com/articles/rust-error-handling/" target="_blank" rel="noopener noreferrer">Error Handling in Rust - Medium Article</a></li>

<li><a href="https://www.rust-lang.org/learn" target="_blank" rel="noopener noreferrer">Rust Programming Language Official Guide</a></li>

</ul>

Comentários

Mais em Rust

O que Todo Dev Deve Saber sobre Biblioteca anyhow: Tratamento de Erros em Aplicações Rust
O que Todo Dev Deve Saber sobre Biblioteca anyhow: Tratamento de Erros em Aplicações Rust

Por Que Tratamento de Erros em Rust é Diferente Em Rust, erros não são exceçõ...

O que Todo Dev Deve Saber sobre Async e Await em Rust: Introdução à Programação Assíncrona
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 pro...

Boas Práticas de Processos e Subprocessos em Rust com std::process para Times Ágeis
Boas Práticas de Processos e Subprocessos em Rust com std::process para Times Ágeis

Introdução aos Processos em Rust A execução de subprocessos é uma necessidade...