---
title: "Error Handling Rust: Libs e Boas Práticas | Rust Brasil"
url: "https://rustlang.com.br/artigos/error-handling-libs/"
markdown_url: "https://rustlang.com.br/artigos/error-handling-libs.MD"
description: "Bibliotecas de error handling em Rust: anyhow, thiserror, eyre, color-eyre e miette. Quando usar cada uma e boas práticas."
date: "2026-02-23"
author: "Equipe Rust Brasil"
---

# Error Handling Rust: Libs e Boas Práticas | Rust Brasil

Bibliotecas de error handling em Rust: anyhow, thiserror, eyre, color-eyre e miette. Quando usar cada uma e boas práticas.


## Introdução

O tratamento de erros é um dos pontos fortes do Rust — o sistema de tipos com `Result<T, E>` e a ausência de exceções forçam o desenvolvedor a lidar com erros de forma explícita. Porém, implementar tipos de erro customizados "na mão" pode ser trabalhoso.

Três bibliotecas surgiram para resolver esse problema, cada uma com uma filosofia diferente:

- **`thiserror`** — facilita a criação de tipos de erro customizados para **bibliotecas**, gerando automaticamente implementações de `std::error::Error` e `Display`
- **`anyhow`** — fornece um tipo de erro genérico para **aplicações**, permitindo propagar qualquer erro com contexto adicional
- **`miette`** — estende o tratamento de erros com **diagnósticos ricos** e formatação visual bonita, ideal para CLIs e ferramentas de desenvolvimento

As três foram criadas por membros proeminentes da comunidade Rust e são amplamente utilizadas em produção.

## Tabela Comparativa

| Característica | thiserror | anyhow | miette |
|---|---|---|---|
| **Autor** | David Tolnay | David Tolnay | Kat Marchán |
| **Uso principal** | Library crates | Application crates | CLIs e ferramentas |
| **Tipo de erro** | Customizado (enum/struct) | `anyhow::Error` genérico | `miette::Report` com diagnósticos |
| **Derive macro** | `#[derive(Error)]` | N/A | `#[derive(Diagnostic)]` |
| **Contexto** | Via `#[from]` e `#[source]` | `.context()` e `.with_context()` | Spans, labels, help, URL |
| **Pretty print** | Não | Não (básico) | Sim (formatação rica) |
| **Performance** | Zero-cost (compile-time) | Mínimo overhead | Overhead para diagnósticos |
| **Backtrace** | Via std | Sim (automático) | Sim |

## Dependências no Cargo.toml

```toml
[dependencies]
thiserror = "2"          # Para tipos de erro customizados
anyhow = "1"             # Para propagação genérica de erros
miette = { version = "7", features = ["fancy"] }  # Para diagnósticos ricos
```

## thiserror: Erros Customizados para Bibliotecas

O `thiserror` usa derive macros para gerar implementações de `std::error::Error`, `Display` e `From`:

```rust
use thiserror::Error;

#[derive(Debug, Error)]
pub enum AppError {
    #[error("Usuário não encontrado: {id}")]
    UsuarioNaoEncontrado { id: u64 },

    #[error("Credenciais inválidas para o usuário '{usuario}'")]
    CredenciaisInvalidas { usuario: String },

    #[error("Erro de banco de dados")]
    Banco(#[from] sqlx::Error),

    #[error("Erro de I/O: {0}")]
    Io(#[from] std::io::Error),

    #[error("Erro de serialização: {0}")]
    Json(#[from] serde_json::Error),

    #[error("Valor inválido: {campo} deve ser {regra}")]
    Validacao { campo: String, regra: String },

    #[error("Erro interno: {0}")]
    Interno(String),
}
```

### Uso em Funções

```rust
use thiserror::Error;
use std::fs;

#[derive(Debug, Error)]
pub enum ConfigError {
    #[error("Arquivo de configuração não encontrado: {caminho}")]
    ArquivoNaoEncontrado { caminho: String },

    #[error("Erro ao ler arquivo: {0}")]
    Io(#[from] std::io::Error),

    #[error("Erro ao fazer parse da configuração: {0}")]
    Parse(#[from] toml::de::Error),

    #[error("Campo obrigatório ausente: '{campo}'")]
    CampoAusente { campo: String },
}

#[derive(serde::Deserialize)]
struct Config {
    host: Option<String>,
    porta: Option<u16>,
}

fn carregar_config(caminho: &str) -> Result<Config, ConfigError> {
    if !std::path::Path::new(caminho).exists() {
        return Err(ConfigError::ArquivoNaoEncontrado {
            caminho: caminho.to_string(),
        });
    }

    let conteudo = fs::read_to_string(caminho)?; // Auto-converte io::Error
    let config: Config = toml::from_str(&conteudo)?; // Auto-converte toml::de::Error

    if config.host.is_none() {
        return Err(ConfigError::CampoAusente {
            campo: "host".to_string(),
        });
    }

    Ok(config)
}
```

### Erros com Source Chain

```rust
use thiserror::Error;

#[derive(Debug, Error)]
pub enum ServicoError {
    #[error("Falha ao processar pedido #{pedido_id}")]
    Processamento {
        pedido_id: u64,
        #[source]
        causa: Box<dyn std::error::Error + Send + Sync>,
    },

    #[error("Timeout ao consultar serviço externo")]
    Timeout {
        #[source]
        source: reqwest::Error,
    },
}
```

## anyhow: Propagação Genérica para Aplicações

O `anyhow` fornece `anyhow::Error` — um tipo que pode conter qualquer erro, com suporte a contexto:

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

// Result<T> é alias para Result<T, anyhow::Error>
fn ler_config(caminho: &str) -> Result<Config> {
    let conteudo = fs::read_to_string(caminho)
        .with_context(|| format!("Falha ao ler arquivo de configuração: {caminho}"))?;

    let config: Config = toml::from_str(&conteudo)
        .context("Falha ao fazer parse do TOML")?;

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

    // ensure! é como assert! mas retorna Err
    ensure!(config.porta > 1023, "Porta deve ser maior que 1023, recebido: {}", config.porta);

    Ok(config)
}

#[derive(serde::Deserialize)]
struct Config {
    host: String,
    porta: u16,
}

fn main() -> Result<()> {
    let config = ler_config("config.toml")?;
    println!("Servidor: {}:{}", config.host, config.porta);
    Ok(())
}
```

### Contexto em Cadeias de Erros

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

async fn iniciar_servidor() -> Result<()> {
    let config = carregar_config()
        .context("Falha ao carregar configuração")?;

    let pool = conectar_banco(&config.database_url)
        .await
        .context("Falha ao conectar ao banco de dados")?;

    let listener = tokio::net::TcpListener::bind(&config.endereco)
        .await
        .with_context(|| format!("Falha ao vincular na porta {}", config.porta))?;

    println!("Servidor iniciado em {}", config.endereco);
    Ok(())
}

async fn carregar_config() -> Result<AppConfig> {
    // ...
    todo!()
}

async fn conectar_banco(url: &str) -> Result<()> {
    // ...
    todo!()
}

struct AppConfig {
    database_url: String,
    endereco: String,
    porta: u16,
}
```

Quando ocorre um erro, o `anyhow` mostra a cadeia completa de contexto:

```
Error: Falha ao carregar configuração

Caused by:
    0: Falha ao fazer parse do TOML
    1: expected value, found eof at line 3 column 1
```

### Downcast para Tipos Específicos

```rust
use anyhow::Result;
use std::io;

fn processar() -> Result<()> {
    // ... pode gerar vários tipos de erro
    todo!()
}

fn main() {
    match processar() {
        Ok(_) => println!("Sucesso"),
        Err(e) => {
            // Tentar fazer downcast para tipo específico
            if let Some(io_err) = e.downcast_ref::<io::Error>() {
                eprintln!("Erro de I/O: {io_err}");
            } else {
                eprintln!("Erro: {e:#}"); // {:#} mostra a cadeia completa
            }
        }
    }
}
```

## miette: Diagnósticos Ricos para CLIs

O `miette` é ideal quando você quer mostrar erros bonitos e informativos, como compiladores e ferramentas de desenvolvimento fazem:

```rust
use miette::{Diagnostic, SourceSpan, NamedSource, Result};
use thiserror::Error;

#[derive(Debug, Error, Diagnostic)]
#[error("Erro de sintaxe no arquivo de configuração")]
#[diagnostic(
    code(config::syntax_error),
    help("Verifique a sintaxe do arquivo. Consulte a documentação em https://exemplo.com/docs"),
    url("https://exemplo.com/erros/E001")
)]
struct ErroSintaxe {
    #[source_code]
    src: NamedSource<String>,

    #[label("erro encontrado aqui")]
    span: SourceSpan,

    #[label("esta seção pode estar relacionada")]
    contexto: Option<SourceSpan>,
}

fn validar_config(conteudo: &str, arquivo: &str) -> Result<()> {
    // Simular erro na posição 45, tamanho 3
    if conteudo.contains("xxx") {
        let pos = conteudo.find("xxx").unwrap();
        return Err(ErroSintaxe {
            src: NamedSource::new(arquivo, conteudo.to_string()),
            span: (pos, 3).into(),
            contexto: None,
        }.into());
    }
    Ok(())
}

fn main() -> Result<()> {
    miette::set_hook(Box::new(|_| {
        Box::new(
            miette::MietteHandlerOpts::new()
                .terminal_links(true)
                .unicode(true)
                .context_lines(2)
                .build(),
        )
    })).unwrap();

    let conteudo = r#"
[servidor]
host = "localhost"
porta = xxx
"#;

    validar_config(conteudo, "config.toml")?;
    Ok(())
}
```

Saída visual:

```
  × Erro de sintaxe no arquivo de configuração
   ╭─[config.toml:4:9]
 3 │ host = "localhost"
 4 │ porta = xxx
   ·         ─┬─
   ·          ╰── erro encontrado aqui
   ╰────
  help: Verifique a sintaxe do arquivo. Consulte a documentação.
  docs: https://exemplo.com/erros/E001
```

### Múltiplos Erros

```rust
use miette::{Diagnostic, Report};
use thiserror::Error;

#[derive(Debug, Error, Diagnostic)]
#[error("Erros de validação encontrados")]
struct ErrosValidacao {
    #[related]
    erros: Vec<ErroValidacao>,
}

#[derive(Debug, Error, Diagnostic)]
#[error("{mensagem}")]
struct ErroValidacao {
    mensagem: String,

    #[help]
    dica: String,
}

fn validar_formulario(nome: &str, email: &str, idade: i32) -> Result<(), Report> {
    let mut erros = vec![];

    if nome.is_empty() {
        erros.push(ErroValidacao {
            mensagem: "Nome é obrigatório".to_string(),
            dica: "Forneça um nome com pelo menos 2 caracteres".to_string(),
        });
    }

    if !email.contains('@') {
        erros.push(ErroValidacao {
            mensagem: format!("Email inválido: '{email}'"),
            dica: "O email deve conter @ e um domínio válido".to_string(),
        });
    }

    if idade < 0 || idade > 150 {
        erros.push(ErroValidacao {
            mensagem: format!("Idade inválida: {idade}"),
            dica: "A idade deve estar entre 0 e 150".to_string(),
        });
    }

    if erros.is_empty() {
        Ok(())
    } else {
        Err(ErrosValidacao { erros }.into())
    }
}
```

## Combinando as Três: Padrão Recomendado

O padrão mais comum em projetos Rust é:

- **`thiserror`** para definir tipos de erro internos da biblioteca
- **`anyhow`** na camada de aplicação para propagação fácil
- **`miette`** quando a CLI precisa de output rico

```rust
// src/errors.rs — tipos de erro com thiserror
use thiserror::Error;

#[derive(Debug, Error)]
pub enum DomainError {
    #[error("Entidade não encontrada: {tipo} com id {id}")]
    NaoEncontrado { tipo: String, id: String },

    #[error("Operação não autorizada")]
    NaoAutorizado,

    #[error("Dados inválidos: {0}")]
    Validacao(String),
}

// src/service.rs — lógica de negócio retorna DomainError
pub fn buscar_usuario(id: &str) -> Result<String, DomainError> {
    if id == "0" {
        return Err(DomainError::NaoEncontrado {
            tipo: "usuario".to_string(),
            id: id.to_string(),
        });
    }
    Ok(format!("Usuario-{id}"))
}
```

```rust
// src/main.rs — aplicação usa anyhow para composição
use anyhow::{Context, Result};

mod errors;
mod service;

fn main() -> Result<()> {
    let usuario = service::buscar_usuario("42")
        .context("Falha ao buscar usuário para processar pedido")?;

    println!("Usuário encontrado: {usuario}");
    Ok(())
}
```

## Integração com Frameworks Web

### Com Axum

```rust
use axum::{
    http::StatusCode,
    response::{IntoResponse, Response},
    Json,
};
use thiserror::Error;
use serde_json::json;

#[derive(Debug, Error)]
pub enum ApiError {
    #[error("Recurso não encontrado")]
    NaoEncontrado,

    #[error("Credenciais inválidas")]
    NaoAutorizado,

    #[error("Dados inválidos: {0}")]
    Validacao(String),

    #[error("Erro interno")]
    Interno(#[from] anyhow::Error),
}

impl IntoResponse for ApiError {
    fn into_response(self) -> Response {
        let (status, mensagem) = match &self {
            ApiError::NaoEncontrado => (StatusCode::NOT_FOUND, self.to_string()),
            ApiError::NaoAutorizado => (StatusCode::UNAUTHORIZED, self.to_string()),
            ApiError::Validacao(_) => (StatusCode::BAD_REQUEST, self.to_string()),
            ApiError::Interno(e) => {
                tracing::error!("Erro interno: {e:#}");
                (StatusCode::INTERNAL_SERVER_ERROR, "Erro interno do servidor".to_string())
            }
        };

        let body = Json(json!({
            "erro": mensagem,
            "status": status.as_u16(),
        }));

        (status, body).into_response()
    }
}
```

## Quando Usar Cada Biblioteca

### Use `thiserror` quando:

- Está escrevendo uma **library crate**
- Precisa de tipos de erro **específicos e tipados**
- Quer que consumidores possam fazer **match** nos seus erros
- Precisa implementar **`From`** para conversão automática com `?`

### Use `anyhow` quando:

- Está escrevendo uma **aplicação** (não uma biblioteca)
- Não precisa que o chamador faça match em tipos de erro específicos
- Quer **propagação fácil** com `.context()`
- Tem muitas fontes de erro diferentes e quer um tipo unificado

### Use `miette` quando:

- Está construindo uma **CLI** ou ferramenta de desenvolvimento
- Quer **output visual bonito** para erros
- Precisa apontar para **localizações específicas** no código/configuração do usuário
- Quer fornecer **dicas** e **links** para documentação junto com erros

### Diagrama de decisão:

```
Está escrevendo uma library?
  ├── Sim → use thiserror
  └── Não → é uma CLI com output rico?
      ├── Sim → use miette (+ thiserror para definições)
      └── Não → use anyhow (+ thiserror para tipos internos)
```

## Conclusão

O ecossistema de error handling do Rust é maduro e ergonômico. A combinação de `thiserror` para definição de tipos e `anyhow` para propagação na aplicação cobre a maioria dos casos. Para CLIs e ferramentas, o `miette` adiciona diagnósticos visuais impressionantes. Dominar essas três bibliotecas é essencial para escrever Rust idiomático e manutenível.

## Veja Também

- [Tutorial: Tratamento de Erros em Rust](/tutoriais/tratamento-de-erros/)
- [Receita: Tratar Erros em Rust](/receitas/tratar-erros/)
- [Receita: Converter Erros](/receitas/converter-erros/)
- [Clap 4: Guia Completo para CLIs](/artigos/clap-vs-structopt/)
- [Tracing vs Log em Rust](/artigos/tracing-vs-log/)
- [Axum vs Actix Web: Qual Framework Escolher](/artigos/axum-vs-actix/)

---

Compare bibliotecas e padrões de tratamento de erros em outras linguagens:

- <a href="https://golang.com.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'golang.com.br' })">Error wrapping em Go: fmt.Errorf, errors.Is e errors.As</a>
- <a href="https://kotlin.dev.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'kotlin.dev.br' })">Exceções em Kotlin: checked vs unchecked, runCatching e Result</a>
