Rust

Boas Práticas de Biblioteca thiserror: Erros Ergonômicos em Rust para Times Ágeis

7 min de leitura

Boas Práticas de Biblioteca thiserror: Erros Ergonômicos em Rust para Times Ágeis

Entendendo a Biblioteca thiserror A biblioteca é um derive macro que simplifica significativamente a criação de tipos de erro customizados em Rust. Enquanto a forma tradicional de implementar a trait exige boilerplate code verboso, automatiza esse processo, deixando seu código mais legível e mantível. A biblioteca é particularmente valiosa em projetos que lidam com múltiplas fontes de erro, como aplicações web, CLIs e bibliotecas. O objetivo principal é reduzir a fricção na criação de erros ergonômicos. Em vez de escrever manualmente implementações de , e outras traits, você usa atributos declarativos que o derive macro processa em tempo de compilação. Isso segue a filosofia Rust de "segurança sem sacrificar expressividade". Por que não usar std::error::Error diretamente? Implementar manualmente exige boilerplate: implementar , , para conversão automática. Com , tudo fica em um único tipo com atributos descritivos. Compare com — muito mais clean. Estrutura Básica e Sintaxe A biblioteca funciona através de atributos aplicados a enums ou structs. O atributo

<h2>Entendendo a Biblioteca thiserror</h2>

<p>A biblioteca <code>thiserror</code> é um derive macro que simplifica significativamente a criação de tipos de erro customizados em Rust. Enquanto a forma tradicional de implementar a trait <code>std::error::Error</code> exige boilerplate code verboso, <code>thiserror</code> automatiza esse processo, deixando seu código mais legível e mantível. A biblioteca é particularmente valiosa em projetos que lidam com múltiplas fontes de erro, como aplicações web, CLIs e bibliotecas.</p>

<p>O objetivo principal é reduzir a fricção na criação de erros ergonômicos. Em vez de escrever manualmente implementações de <code>Display</code>, <code>From</code> e outras traits, você usa atributos declarativos que o derive macro processa em tempo de compilação. Isso segue a filosofia Rust de &quot;segurança sem sacrificar expressividade&quot;.</p>

<h3>Por que não usar std::error::Error diretamente?</h3>

<p>Implementar manualmente <code>std::error::Error</code> exige boilerplate: implementar <code>Display</code>, <code>Debug</code>, <code>From</code> para conversão automática. Com <code>thiserror</code>, tudo fica em um único tipo com atributos descritivos.</p>

<pre><code class="language-rust">// Sem thiserror - verboso

use std::error::Error;

use std::fmt;

#[derive(Debug)]

enum MinhaErro {

IoError(std::io::Error),

ParseError(String),

}

impl fmt::Display for MinhaErro {

fn fmt(&amp;self, f: &amp;mut fmt::Formatter) -&gt; fmt::Result {

match self {

MinhaErro::IoError(e) =&gt; write!(f, &quot;Erro de IO: {}&quot;, e),

MinhaErro::ParseError(msg) =&gt; write!(f, &quot;Erro de parse: {}&quot;, msg),

}

}

}

impl Error for MinhaErro {}

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

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

MinhaErro::IoError(err)

}

}</code></pre>

<p>Compare com <code>thiserror</code> — muito mais clean.</p>

<h2>Estrutura Básica e Sintaxe</h2>

<p>A biblioteca funciona através de atributos aplicados a enums ou structs. O atributo <code>#[error(...)]</code> define como cada variante será exibida quando convertida em string, enquanto <code>#[from]</code> gera automaticamente implementações de <code>From</code>.</p>

<pre><code class="language-rust">use thiserror::Error;

#[derive(Error, Debug)]

pub enum ConfigError {

#[error(&quot;Arquivo não encontrado: {0}&quot;)]

FileNotFound(String),

#[error(&quot;Erro de parsing TOML: {0}&quot;)]

TomlError(#[from] toml::de::Error),

#[error(&quot;Valor inválido para &#039;{key}&#039;: {value}&quot;)]

InvalidValue { key: String, value: String },

#[error(&quot;Erro de I/O&quot;)]

IoError(#[from] std::io::Error),

}</code></pre>

<p>Neste exemplo, <code>#[from]</code> permite conversão automática de <code>toml::de::Error</code> e <code>std::io::Error</code> para <code>ConfigError</code>. Quando você retorna <code>?</code> sobre esses tipos, a conversão acontece implicitamente. O atributo <code>#[error(...)]</code> define exatamente como cada erro será exibido ao usuário.</p>

<h3>Usando #[source] para Error Chaining</h3>

<p>O atributo <code>#[source]</code> é crucial para criar chains de erro que preservam a causa raiz. Isso permite que ferramentas de diagnóstico e logs rastreiem o caminho completo do erro.</p>

<pre><code class="language-rust">use thiserror::Error;

#[derive(Error, Debug)]

pub enum ProcessingError {

#[error(&quot;Falha ao processar arquivo&quot;)]

ProcessingFailed {

#[from]

source: std::io::Error,

},

#[error(&quot;Validação falhou: {message}&quot;)]

ValidationFailed {

message: String,

#[source]

cause: Box&lt;dyn std::error::Error + Send + Sync&gt;,

},

}</code></pre>

<p>O <code>#[source]</code> permite que bibliotecas como <code>anyhow</code> e <code>eyre</code> façam backtrace completo dos erros. Quando você imprime o erro com <code>.source()</code>, consegue navegar toda a cadeia.</p>

<h2>Padrões Avançados</h2>

<h3>Combinando com Result Type Alias</h3>

<p>Um padrão muito comum é criar um type alias <code>Result</code> para sua aplicação, eliminando a necessidade de sempre escrever <code>Result&lt;T, SeuErro&gt;</code>.</p>

<pre><code class="language-rust">use thiserror::Error;

#[derive(Error, Debug)]

pub enum ApiError {

#[error(&quot;Requisição HTTP falhou: {0}&quot;)]

HttpError(#[from] reqwest::Error),

#[error(&quot;Resposta inválida JSON: {0}&quot;)]

JsonError(#[from] serde_json::Error),

#[error(&quot;Servidor retornou status {code}: {message}&quot;)]

ServerError { code: u16, message: String },

}

pub type ApiResult&lt;T&gt; = Result&lt;T, ApiError&gt;;

// Uso muito mais limpo

pub async fn fetch_user(id: u64) -&gt; ApiResult&lt;User&gt; {

let response = reqwest::get(&amp;format!(&quot;https://api.example.com/users/{}&quot;, id)).await?;

let user = response.json::&lt;User&gt;().await?;

Ok(user)

}</code></pre>

<p>Agora <code>ApiResult&lt;T&gt;</code> é o idioma padrão do seu código, reduzindo ruído visual e tornando assinaturas de função mais expressivas.</p>

<h3>Erros Transparentes com #[error]</h3>

<p>Para casos onde você quer que um tipo de erro simplesmente passe através com a mensagem do erro subjacente, use <code>#[error(transparent)]</code>.</p>

<pre><code class="language-rust">use thiserror::Error;

#[derive(Error, Debug)]

#[error(transparent)]

pub struct ParseError(#[from] std::num::ParseIntError);

// Agora ParseError se comporta exatamente como ParseIntError,

// apenas adicionando segurança de tipo

fn parse_number(s: &amp;str) -&gt; Result&lt;i32, ParseError&gt; {

Ok(s.parse()?)

}</code></pre>

<h2>Conclusão</h2>

<p>A biblioteca <code>thiserror</code> resolve um problema real de ergonomia em Rust: criação de erros customizados sem boilerplate excessivo. Três aprendizados principais: <strong>primeiro</strong>, o derive macro <code>#[derive(Error)]</code> + atributo <code>#[error(...)]</code> elimina a necessidade de implementar <code>Display</code> manualmente; <strong>segundo</strong>, <code>#[from]</code> fornece conversão automática entre tipos de erro, permitindo usar <code>?</code> livremente; <strong>terceiro</strong>, <code>#[source]</code> preserva a cadeia de erro para debugging eficaz, especialmente importante em aplicações production-grade.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://docs.rs/thiserror/latest/thiserror/" target="_blank" rel="noopener noreferrer">Documentação Oficial thiserror</a></li>

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

<li><a href="https://docs.rs/anyhow/latest/anyhow/" target="_blank" rel="noopener noreferrer">anyhow - Flexible concrete Error type</a></li>

<li><a href="https://www.oreilly.com/library/view/effective-rust/9781098109288/" target="_blank" rel="noopener noreferrer">Effective Rust - Error Handling</a></li>

<li><a href="https://github.com/rust-lang/rustlings" target="_blank" rel="noopener noreferrer">Rustlings Exercises</a></li>

</ul>

Comentários

Mais em Rust

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...

Guia Completo de Box<T> em Rust: Alocação Explícita no Heap
Guia Completo de Box<T> em Rust: Alocação Explícita no Heap

O que é Box e Por Que Usar? Box é um tipo de dado inteligente (smart pointer)...

Guia Completo de Iteradores Customizados: Implementando o Trait Iterator
Guia Completo de Iteradores Customizados: Implementando o Trait Iterator

Por que Implementar Iteradores Customizados? Iteradores são fundamentais em l...