---
title: "Tratamento de Erros em Rust: thiserror, anyhow e Boas Práticas em 2026"
url: "https://rustlang.com.br/blog/tratamento-erros-rust-thiserror-anyhow/"
markdown_url: "https://rustlang.com.br/blog/tratamento-erros-rust-thiserror-anyhow.MD"
description: "Domine o tratamento de erros em Rust com thiserror, anyhow, operador ? e custom errors. Guia completo com exemplos práticos e boas práticas."
date: "2026-03-29"
author: "Equipe Rust Brasil"
---

# Tratamento de Erros em Rust: thiserror, anyhow e Boas Práticas em 2026

Domine o tratamento de erros em Rust com thiserror, anyhow, operador ? e custom errors. Guia completo com exemplos práticos e boas práticas.


## Introdução

O tratamento de erros é uma das áreas onde Rust mais se diferencia de outras linguagens. Enquanto linguagens como Java e Python usam exceções que podem surgir em qualquer ponto do código, e Go retorna tuplas `(valor, error)` sem verificação em tempo de compilação, Rust torna os erros **parte do sistema de tipos** com `Result<T, E>` — se uma função pode falhar, o compilador obriga você a lidar com isso.

Mas trabalhar apenas com `Result` e tipos de erro manuais pode ser verboso. É aí que entram crates como **thiserror** e **anyhow**, que simplificam drasticamente o tratamento de erros sem sacrificar a segurança de tipos. Neste guia, vamos desde os fundamentos do `Result` até padrões avançados usados em produção.

Se você ainda está aprendendo os conceitos básicos de Rust, recomendamos começar pelo nosso [tutorial de tratamento de erros](/tutoriais/tratamento-de-erros/) antes de se aprofundar neste artigo.

## Result e Option — A Base de Tudo

Em Rust, o tipo `Result<T, E>` representa uma operação que pode ter sucesso (`Ok(T)`) ou falhar (`Err(E)`). Já o `Option<T>` representa um valor que pode existir (`Some(T)`) ou não (`None`). Para uma referência completa desses tipos, veja nossos guias sobre [Result](/stdlib/result/) e [Option](/stdlib/option/).

```rust
use std::fs;
use std::num::ParseIntError;

fn ler_numero_do_arquivo(caminho: &str) -> Result<i32, String> {
    // Lê o conteúdo do arquivo
    let conteudo = fs::read_to_string(caminho)
        .map_err(|e| format!("Erro ao ler arquivo: {}", e))?;

    // Converte para número
    let numero: i32 = conteudo.trim().parse()
        .map_err(|e: ParseIntError| format!("Erro ao converter: {}", e))?;

    Ok(numero)
}
```

O operador `?` propaga o erro automaticamente, mas note como precisamos usar `map_err` para converter entre tipos de erro diferentes. Isso funciona, mas fica verboso rapidamente.

## Erros Customizados com enum

A abordagem idiomática em Rust é criar um enum que represente todos os erros possíveis do seu módulo:

```rust
use std::fmt;
use std::io;
use std::num::ParseIntError;

#[derive(Debug)]
enum MeuErro {
    Io(io::Error),
    Parse(ParseIntError),
    Validacao(String),
}

impl fmt::Display for MeuErro {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            MeuErro::Io(e) => write!(f, "Erro de I/O: {}", e),
            MeuErro::Parse(e) => write!(f, "Erro de parsing: {}", e),
            MeuErro::Validacao(msg) => write!(f, "Validação: {}", msg),
        }
    }
}

impl std::error::Error for MeuErro {}

// Conversões automáticas com From
impl From<io::Error> for MeuErro {
    fn from(e: io::Error) -> Self {
        MeuErro::Io(e)
    }
}

impl From<ParseIntError> for MeuErro {
    fn from(e: ParseIntError) -> Self {
        MeuErro::Parse(e)
    }
}
```

Isso é correto e seguro, mas são **mais de 30 linhas de boilerplate** para três variantes de erro. Imagine um projeto real com dezenas de tipos de erro — é aqui que o thiserror brilha.

## thiserror — Erros Tipados sem Boilerplate

O [thiserror](https://crates.io/crates/thiserror) é uma crate de derive macro que gera automaticamente as implementações de `Display`, `Error` e `From`. É a escolha padrão para **bibliotecas** e código que precisa de erros tipados. Para entender como derive macros funcionam por baixo dos panos, confira nosso artigo sobre [macros em Rust](/artigos/macros-rust/).

```rust
use thiserror::Error;

#[derive(Error, Debug)]
enum AppErro {
    #[error("Erro ao acessar arquivo: {0}")]
    Io(#[from] std::io::Error),

    #[error("Formato inválido: {0}")]
    Parse(#[from] std::num::ParseIntError),

    #[error("Validação falhou: {mensagem}")]
    Validacao { mensagem: String },

    #[error("Usuário '{nome}' não encontrado (id: {id})")]
    UsuarioNaoEncontrado { nome: String, id: u64 },

    #[error("Conexão com o banco falhou após {tentativas} tentativas")]
    ConexaoBanco { tentativas: u32 },
}
```

Com thiserror, o enum acima gera automaticamente:
- `impl Display` usando as strings do atributo `#[error(...)]`
- `impl Error` com a trait `std::error::Error`
- `impl From<std::io::Error>` e `impl From<ParseIntError>` graças ao `#[from]`

Agora a função anterior fica muito mais limpa:

```rust
fn ler_numero_do_arquivo(caminho: &str) -> Result<i32, AppErro> {
    let conteudo = std::fs::read_to_string(caminho)?; // ? converte io::Error automaticamente
    let numero: i32 = conteudo.trim().parse()?;        // ? converte ParseIntError automaticamente

    if numero < 0 {
        return Err(AppErro::Validacao {
            mensagem: "Número deve ser positivo".into(),
        });
    }

    Ok(numero)
}
```

### Padrões Avançados com thiserror

O thiserror suporta `#[source]` para encadear erros e `#[backtrace]` para capturar stack traces:

```rust
#[derive(Error, Debug)]
enum ServicoErro {
    #[error("Falha na requisição HTTP")]
    Http {
        #[source]
        causa: reqwest::Error,
        url: String,
    },

    #[error("Configuração inválida: {0}")]
    Config(String),

    #[error(transparent)] // Delega Display e source para o erro interno
    Outro(#[from] anyhow::Error),
}
```

## anyhow — Erros Flexíveis para Aplicações

Enquanto o thiserror é ideal para bibliotecas com erros tipados, o [anyhow](https://crates.io/crates/anyhow) é projetado para **aplicações** onde você quer simplicidade máxima. Ele fornece o tipo `anyhow::Result<T>` que aceita qualquer erro implementando `std::error::Error`. Para uma análise completa dessas duas crates, veja nosso guia do [ecossistema anyhow e thiserror](/ecossistema/anyhow-thiserror/).

```rust
use anyhow::{Context, Result, bail, ensure};

fn processar_config(caminho: &str) -> Result<Config> {
    let conteudo = std::fs::read_to_string(caminho)
        .context("Falha ao ler arquivo de configuração")?;

    let config: Config = toml::from_str(&conteudo)
        .context("TOML inválido no arquivo de configuração")?;

    // bail! retorna Err imediatamente com uma mensagem
    if config.porta == 0 {
        bail!("Porta não pode ser zero");
    }

    // ensure! é como assert! mas retorna Err em vez de panic
    ensure!(config.porta < 65536, "Porta {} fora do intervalo válido", config.porta);

    Ok(config)
}
```

O método `.context()` é um dos recursos mais poderosos do anyhow — ele adiciona **contexto humano** aos erros, criando uma cadeia que facilita o diagnóstico:

```
Error: Falha ao processar pedido #42

Caused by:
    0: Falha ao ler arquivo de configuração
    1: No such file or directory (os error 2)
```

### Quando Usar anyhow vs thiserror

| Cenário | Recomendação |
|---------|-------------|
| Biblioteca pública (crate) | **thiserror** — consumidores precisam fazer match nos erros |
| Aplicação binária | **anyhow** — simplicidade e contexto são mais importantes |
| Código interno de empresa | **anyhow** na maioria dos casos |
| API REST handlers | **thiserror** para mapear erros em status HTTP |
| Scripts e CLIs | **anyhow** — reportar erros é suficiente |

Na prática, muitos projetos usam **ambos**: thiserror para definir erros da camada de domínio e anyhow na camada de aplicação.

## O Operador ? em Profundidade

O operador `?` é syntactic sugar que faz muito mais do que parece. Ele chama `From::from()` no erro para converter entre tipos, permitindo composição elegante. Para entender como o trait `From` funciona, veja nosso guia sobre [From e Into](/stdlib/from-into/).

```rust
use anyhow::{Context, Result};

// Composição de múltiplas operações falíveis
async fn buscar_usuario(id: u64) -> Result<Usuario> {
    let url = format!("https://api.exemplo.com/usuarios/{}", id);

    let resposta = reqwest::get(&url)
        .await
        .context("Falha na requisição HTTP")?;

    let usuario: Usuario = resposta
        .json()
        .await
        .context("Resposta não é JSON válido")?;

    // Validação com ensure!
    anyhow::ensure!(!usuario.nome.is_empty(), "Nome do usuário está vazio");

    Ok(usuario)
}
```

Se você está trabalhando com código assíncrono, o operador `?` funciona perfeitamente com `async/await` — veja nosso guia completo sobre [async Rust](/blog/async-rust-ecossistema-2026/).

## Padrão: Erros por Camada

Em aplicações maiores, um padrão comum é definir tipos de erro por camada usando thiserror e converter para anyhow nas bordas:

```rust
// Camada de repositório
#[derive(Error, Debug)]
enum RepoErro {
    #[error("Registro não encontrado: {0}")]
    NaoEncontrado(String),
    #[error("Violação de constraint: {0}")]
    Constraint(String),
    #[error("Erro de banco de dados")]
    Database(#[from] sqlx::Error),
}

// Camada de serviço
#[derive(Error, Debug)]
enum ServicoErro {
    #[error("Usuário não encontrado")]
    UsuarioNaoEncontrado,
    #[error("Email já cadastrado: {0}")]
    EmailDuplicado(String),
    #[error(transparent)]
    Interno(#[from] RepoErro),
}

// Camada de API — converte para respostas HTTP
impl axum::response::IntoResponse for ServicoErro {
    fn into_response(self) -> axum::response::Response {
        let (status, mensagem) = match &self {
            ServicoErro::UsuarioNaoEncontrado => {
                (StatusCode::NOT_FOUND, self.to_string())
            }
            ServicoErro::EmailDuplicado(_) => {
                (StatusCode::CONFLICT, self.to_string())
            }
            ServicoErro::Interno(_) => {
                (StatusCode::INTERNAL_SERVER_ERROR, "Erro interno".into())
            }
        };
        (status, Json(json!({ "erro": mensagem }))).into_response()
    }
}
```

Para aprender mais sobre como integrar esse padrão com Axum, confira nosso artigo [Axum Web Framework](/blog/axum-web-framework-rust-2026/).

## Boas Práticas

1. **Nunca use `.unwrap()` em produção** — use `.expect("mensagem descritiva")` ou trate o erro. Veja nosso guia sobre [erros com panic e unwrap](/erros/panic-unwrap/).

2. **Prefira `?` sobre `match`** quando só precisa propagar o erro.

3. **Adicione contexto** com `.context()` ou `.with_context(|| ...)` para erros que cruzam fronteiras de módulo.

4. **Defina erros tipados** para interfaces públicas — consumidores da sua API precisam fazer pattern matching.

5. **Use `#[error(transparent)]`** quando uma variante de erro é apenas um wrapper.

6. **Teste seus erros** — verifique que as mensagens são úteis e que os tipos corretos são retornados. Veja nosso [tutorial de testes em Rust](/tutoriais/testes-rust/).

```rust
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn teste_erro_validacao() {
        let resultado = validar_email("");
        assert!(resultado.is_err());
        let erro = resultado.unwrap_err();
        assert!(matches!(erro, AppErro::Validacao { .. }));
        assert!(erro.to_string().contains("Email"));
    }
}
```

## Comparação com Outras Linguagens

O modelo de erros do Rust é frequentemente comparado ao de Go e ao de linguagens com exceções. Diferente de <a href="https://golang.com.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'golang.com.br' })">Go</a>, onde o padrão `if err != nil` não é verificado pelo compilador, Rust garante em tempo de compilação que todos os erros sejam tratados. E diferente de <a href="https://python.dev.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'python.dev.br' })">Python</a>, onde exceções podem surgir de qualquer lugar sem aviso, em Rust o tipo `Result` torna os caminhos de erro explícitos na assinatura da função.

Para comparações detalhadas entre Rust e outras linguagens, confira nossos artigos [Rust vs Go](/blog/rust-vs-go-2026/) e [Rust vs Python](/blog/rust-vs-python-2026/).

## Conclusão

O tratamento de erros em Rust é robusto por design. Com o `Result<T, E>` como fundação, o **thiserror** para erros tipados e o **anyhow** para aplicações, você tem ferramentas para cobrir qualquer cenário — desde bibliotecas públicas até aplicações complexas em produção, incluindo projetos de [CLI](/artigos/rust-para-cli/) e [microsserviços](/artigos/rust-para-microsservicos/).

A regra de ouro é: **thiserror para bibliotecas, anyhow para aplicações, e contexto sempre**. Com essas três diretrizes, seus erros serão informativos, tipados e fáceis de depurar.

Se você quer se aprofundar mais, confira também nosso artigo sobre [boas práticas de error handling](/artigos/boas-praticas-error-handling/) e o guia completo de [Macros em Rust](/blog/macros-rust-declarativas-procedurais-2026/) para entender como o derive do thiserror funciona internamente.

## Veja Também

- [Error Handling: Bibliotecas do Ecossistema](/artigos/error-handling-libs/)
- [Iteradores em Rust](/artigos/iteradores-rust/) — combinam perfeitamente com `Result` via `collect()`
- [Serde: Guia Completo](/artigos/serde-guia-completo/) — serialização com tratamento de erros
- [CI/CD para projetos Rust](/artigos/ci-cd-rust/) — automatize a verificação de erros
- Para explorar tratamento de erros em outras linguagens do ecossistema de sistemas, veja também <a href="https://ziglang.com.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'ziglang.com.br' })">Zig Brasil</a>
