Capítulo 16: Tratamento Idiomático de Erros, Anyhow e Thiserror
Capítulo 16: Tratamento Idiomático de Erros, Anyhow e Thiserror
Especialização em Sistemas com Rust • Rust 2021 Edition • Ownership, Concorrência Segura e Sistemas de Alta Performance
🗺️ Mapa Conceitual da Arquitetura Corporativa de Tratamento de Erros
flowchart TD
OrigemErro["Origem do Erro (I/O, Parsing, Rede, Regra de Negócio)"] --> Classificacao["Decisão de Design"]
subgraph CRATES_LIVRARIAS["Camada de Domínio / Bibliotecas"]
ThiserrorCrate["thiserror: Enums Fortemente Tipados com #[derive(Error)]"]
VariantesEspecificas["Variantes Específicas: ErroAuth, ErroDB, ErroValidacao"]
ImplementaStd["Implementação Automática de std::error::Error + Display"]
end
subgraph APLICACOES_BINA["Camada de Aplicação / Binários / CLI"]
AnyhowCrate["anyhow::Result<T>: Tipo Dinâmico de Alta Ergonomia"]
Contexto[".context('Mensagem Explicativa'): Encadeamento de Causa"]
Backtraces["Backtraces Completos em Falhas para Diagnóstico SRE"]
end
Classificacao -->|"Bibliotecas / Core"| CRATES_LIVRARIAS
Classificacao -->|"Aplicações / Serviços"| APLICACOES_BINA
CRATES_LIVRARIAS -->|"Propagação com '?'"| APLICACOES_BINA
APLICACOES_BINA --> LogsRelatorios["Relatórios Limpos de Produção e Observabilidade"]
style OrigemErro fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style Classificacao fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style CRATES_LIVRARIAS fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style APLICACOES_BINA fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style LogsRelatorios fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px
🏛️ 1. Filosofia de Resiliência e Tratamento de Erros
Em arquitetura de sistemas corporativos, um dos maiores desafios é garantir que falhas transientes (como timeout de rede, arquivo ausente ou dados corrompidos) sejam tratadas graciosamente, sem derrubar a aplicação.
O Rust adota uma filosofia transparente dividida em duas categorias:
- Erros Irrecuperáveis (
panic!): Reservados para bugs graves de programação (como indexação fora dos limites de um array ou violação de invariantes críticas do sistema). O processo é abortado ou a stack é desenrolada. - Erros Recuperáveis (
Result<T, E>): O padrão para 99% das operações do mundo real. O erro faz parte explícita da assinatura de tipos da função, obrigando o chamador a tratá-lo antes de compilar.
⚙️ 2. A Trait Canônica std::error::Error
Para que um tipo seja reconhecido oficialmente como um erro pelo ecossistema do Rust, ele deve implementar três traits:
Debug: Para formatação técnica interna.Display: Para mensagens amigáveis ao usuário final.std::error::Error: Trait que define a inspeção de causas subjacentes com o método opcional.source().
📚 3. Tratamento de Erros em Bibliotecas com thiserror
Ao desenvolver bibliotecas, crates modulares ou a camada de domínio da aplicação, os erros devem ser fortemente tipados, permitindo que os consumidores façam pattern matching sobre as causas exatas do erro.
A crate thiserror elimina dezenas de linhas de boilerplate gerando as implementações de Display e Error automaticamente via macros procedurais:
// Cargo.toml: thiserror = "1.0"
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ErroCluster {
#[error("Falha de I/O no disco do nó {no_id}: {source}")]
FalhaDisco {
no_id: u32,
#[source]
source: std::io::Error, // Encadeamento automático de causa
},
#[error("Timeout de conexão após {0} ms")]
TimeoutRede(u64),
#[error("Capacidade máxima do pool atingida ({limite} conexões)")]
PoolExcedido { limite: u32 },
#[error("Resposta do servidor não pôde ser parseada como JSON válido")]
JsonInvalido(#[from] serde_json::Error), // Converte automaticamente com '?'!
}
🚀 4. Tratamento de Erros em Aplicações com anyhow
Enquanto bibliotecas precisam de enums rígidos, no nível de aplicação (como comandos CLI, controladores web ou tarefas em background), o que você mais precisa é de ergonomia, rapidez e adição de contexto sobre onde a falha ocorreu.
A crate anyhow fornece o tipo anyhow::Result<T> e métodos de enriquecimento com .context():
// Cargo.toml: anyhow = "1.0"
use anyhow::{Context, Result};
use std::fs::File;
fn inicializar_configuracao_empresa(caminho: &str) -> Result<Config> {
// .with_context adiciona mensagens ricas sem perder a causa raiz original
let arquivo = File::open(caminho)
.with_context(|| format!("Não foi possível abrir o arquivo de configuração em '{}'", caminho))?;
let config: Config = serde_json::from_reader(arquivo)
.context("O conteúdo do arquivo JSON de configuração está corrompido ou incompatível")?;
Ok(config)
}
Se o arquivo não existir, o anyhow exibirá no log a pilha explicativa completa:
Error: Não foi possível abrir o arquivo de configuração em '/etc/app/config.json'
Caused by:
No such file or directory (os error 2)
⚖️ 5. A Regra de Ouro: thiserror vs anyhow
| Critério | thiserror |
anyhow |
|---|---|---|
| Onde Utilizar | Bibliotecas, Crates, Camada de Domínio | Aplicações Finais, Binários, CLIs, APIs |
| Tipo de Erro | Enums fortemente tipados (ErroAuth, ErroDB) |
Objeto dinâmico (anyhow::Error / Result<T>) |
| Objetivo Principal | Permitir ao chamador inspecionar e tratar cada caso com match |
Facilitar a propagação rápida com ? e enriquecer com .context() |
| Flexibilidade | Rígida e previsível | Extremamente ergonômica |
🛠️ 6. Combinadores Funcionais em Result e Option
Rust permite encadear operações em resultados sem recorrer a múltiplos blocos condicionais:
// 1. .map() e .map_err(): Transforma o valor ou o erro
let numero: Result<i32, _> = "42".parse().map(|n: i32| n * 2);
// 2. .and_then(): Encadeia operações falíveis (Monadic FlatMap)
let resultado = buscar_usuario_id(10)
.and_then(|u| carregar_carteira(u.carteira_id));
// 3. .unwrap_or_else(): Recuperação graciosa com fallback
let porta: u16 = std::env::var("PORTA")
.ok() // Transforma Result em Option
.and_then(|p| p.parse().ok())
.unwrap_or(8080); // Porta padrão caso não esteja configurada
⚠️ 7. Armadilhas Comuns e Anti-Patterns
- Retornar
Result<(), String>:- Anti-Pattern: Usar strings como tipo de erro (
Err("Falhou")). Strings não implementamstd::error::Error, perdem tipos e dificultam o tratamento programático. - Correção: Use enums com
thiserrorou adoteanyhow::Result.
- Anti-Pattern: Usar strings como tipo de erro (
- Ignorar Erros Silenciosamente com
let _ = ...:- Descartar o
Resultsem inspecioná-lo oculta falhas críticas de infraestrutura. Trate comif let Err(e) = ...e registre em logs.
- Descartar o
🛡️ 8. Boas Práticas de Observabilidade
- Ative Backtraces em Ambientes de Teste: Configure a variável de ambiente
RUST_BACKTRACE=1para inspecionar o rastreamento completo da linha de código em caso de panics durante testes unitários.
📋 9. Cheat-Sheet: Resumo Executivo
// Biblioteca: thiserror
#[derive(thiserror::Error, Debug)]
pub enum MyError {
#[error("Timeout: {0}s")]
Timeout(u32),
}
// Aplicação: anyhow
use anyhow::{Context, Result};
fn run() -> Result<()> {
operacao().context("Falha na etapa de inicialização")?;
Ok(())
}