Em projetos reais, suas funções lidam com múltiplos tipos de erro — IO, parsing, validação, rede. Converter entre eles é essencial para que o operador ? funcione de forma fluida. Nesta receita, você vai aprender todas as estratégias de conversão de erros em Rust, desde o trait From manual até as crates thiserror e anyhow.
Dependências
[package]
name = "receita-converter-erros"
version = "0.1.0"
edition = "2021"
[dependencies]
thiserror = "2"
anyhow = "1"
Código Completo
use std::fmt;
use std::io;
use std::num::ParseIntError;
// =============================================
// 1. Abordagem manual com From trait
// =============================================
#[derive(Debug)]
enum ConfigErro {
Io(io::Error),
Parse(ParseIntError),
Validacao(String),
}
impl fmt::Display for ConfigErro {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ConfigErro::Io(e) => write!(f, "Erro de IO: {}", e),
ConfigErro::Parse(e) => write!(f, "Erro de parse: {}", e),
ConfigErro::Validacao(msg) => write!(f, "Validação: {}", msg),
}
}
}
impl std::error::Error for ConfigErro {}
// Implementar From para cada tipo de erro que queremos converter
impl From<io::Error> for ConfigErro {
fn from(e: io::Error) -> Self {
ConfigErro::Io(e)
}
}
impl From<ParseIntError> for ConfigErro {
fn from(e: ParseIntError) -> Self {
ConfigErro::Parse(e)
}
}
fn ler_porta_manual(conteudo: &str) -> Result<u16, ConfigErro> {
// O ? converte ParseIntError em ConfigErro via From
let porta: u16 = conteudo.trim().parse()?;
if porta < 1024 {
return Err(ConfigErro::Validacao(
format!("Porta {} é reservada (use >= 1024)", porta),
));
}
Ok(porta)
}
// =============================================
// 2. Box<dyn Error> — abordagem rápida e flexível
// =============================================
type ResultDyn<T> = Result<T, Box<dyn std::error::Error>>;
fn ler_porta_box(conteudo: &str) -> ResultDyn<u16> {
let porta: u16 = conteudo.trim().parse()?; // qualquer erro é aceito
Ok(porta)
}
// =============================================
// 3. thiserror — macro para erros customizados
// =============================================
#[derive(Debug, thiserror::Error)]
enum AppErro {
#[error("Arquivo não encontrado: {0}")]
ArquivoNaoEncontrado(String),
#[error("Erro de parse: {0}")]
Parse(#[from] ParseIntError), // #[from] gera impl From automaticamente
#[error("Erro de IO: {0}")]
Io(#[from] io::Error), // #[from] gera impl From automaticamente
#[error("Configuração inválida: {campo} = {valor}")]
ConfigInvalida { campo: String, valor: String },
#[error("Erro interno: {0}")]
Interno(String),
}
fn processar_config_thiserror(conteudo: &str) -> Result<u16, AppErro> {
let porta: u16 = conteudo.trim().parse()?; // usa From<ParseIntError>
if porta == 0 {
return Err(AppErro::ConfigInvalida {
campo: "porta".into(),
valor: "0".into(),
});
}
Ok(porta)
}
// =============================================
// 4. anyhow — para aplicações (não bibliotecas)
// =============================================
fn processar_config_anyhow(conteudo: &str) -> anyhow::Result<u16> {
use anyhow::Context;
let porta: u16 = conteudo
.trim()
.parse()
.context("Falha ao converter porta para número")?;
anyhow::ensure!(porta >= 1024, "Porta {} é reservada (use >= 1024)", porta);
Ok(porta)
}
fn inicializar_app() -> anyhow::Result<()> {
use anyhow::Context;
let porta = processar_config_anyhow("8080")
.context("Falha ao configurar servidor")?;
println!("Servidor configurado na porta {}", porta);
Ok(())
}
fn main() {
println!("=== 1. From trait manual ===");
let testes = vec!["8080", "abc", "80", "3000"];
for teste in &testes {
match ler_porta_manual(teste) {
Ok(porta) => println!(" '{}' -> porta {}", teste, porta),
Err(e) => println!(" '{}' -> ERRO: {}", teste, e),
}
}
println!("\n=== 2. Box<dyn Error> ===");
match ler_porta_box("8080") {
Ok(porta) => println!(" Box<dyn>: porta {}", porta),
Err(e) => println!(" Box<dyn>: erro {}", e),
}
match ler_porta_box("abc") {
Ok(porta) => println!(" Box<dyn>: porta {}", porta),
Err(e) => println!(" Box<dyn>: erro {}", e),
}
println!("\n=== 3. thiserror ===");
let testes = vec!["8080", "abc", "0"];
for teste in &testes {
match processar_config_thiserror(teste) {
Ok(porta) => println!(" '{}' -> porta {}", teste, porta),
Err(e) => println!(" '{}' -> ERRO: {}", teste, e),
}
}
// Demonstrar pattern matching com thiserror
let resultado = processar_config_thiserror("abc");
if let Err(ref e) = resultado {
match e {
AppErro::Parse(parse_err) => {
println!(" -> Erro de parse detectado: {}", parse_err);
}
AppErro::ConfigInvalida { campo, valor } => {
println!(" -> Config inválida: {} = {}", campo, valor);
}
_ => println!(" -> Outro erro: {}", e),
}
}
println!("\n=== 4. anyhow ===");
match processar_config_anyhow("8080") {
Ok(porta) => println!(" anyhow OK: porta {}", porta),
Err(e) => println!(" anyhow ERRO: {}", e),
}
match processar_config_anyhow("abc") {
Ok(porta) => println!(" anyhow OK: porta {}", porta),
Err(e) => println!(" anyhow ERRO: {}", e),
}
match processar_config_anyhow("80") {
Ok(porta) => println!(" anyhow OK: porta {}", porta),
Err(e) => println!(" anyhow ERRO: {}", e),
}
// anyhow com cadeia de contextos
println!("\n=== 5. Cadeia de contextos (anyhow) ===");
if let Err(e) = inicializar_app() {
eprintln!("Erro: {}", e);
for causa in e.chain().skip(1) {
eprintln!(" Causado por: {}", causa);
}
}
println!("\n=== 6. map_err para conversão pontual ===");
let resultado: Result<u16, String> = "8080"
.parse::<u16>()
.map_err(|e| format!("Não foi possível converter: {}", e));
println!(" map_err: {:?}", resultado);
let resultado: Result<u16, String> = "abc"
.parse::<u16>()
.map_err(|e| format!("Não foi possível converter: {}", e));
println!(" map_err: {:?}", resultado);
}
Saída do Programa
=== 1. From trait manual ===
'8080' -> porta 8080
'abc' -> ERRO: Erro de parse: invalid digit found in string
'80' -> ERRO: Validação: Porta 80 é reservada (use >= 1024)
'3000' -> porta 3000
=== 2. Box<dyn Error> ===
Box<dyn>: porta 8080
Box<dyn>: erro invalid digit found in string
=== 3. thiserror ===
'8080' -> porta 8080
'abc' -> ERRO: Erro de parse: invalid digit found in string
'0' -> ERRO: Configuração inválida: porta = 0
-> Erro de parse detectado: invalid digit found in string
=== 4. anyhow ===
anyhow OK: porta 8080
anyhow ERRO: Falha ao converter porta para número
anyhow ERRO: Porta 80 é reservada (use >= 1024)
=== 5. Cadeia de contextos (anyhow) ===
Servidor configurado na porta 8080
=== 6. map_err para conversão pontual ===
map_err: Ok(8080)
map_err: Err("Não foi possível converter: invalid digit found in string")
Quando Usar Cada Abordagem
| Abordagem | Ideal para | Vantagens |
|---|---|---|
From manual | Aprendizado, controle total | Total controle sobre conversão |
Box<dyn Error> | Protótipos rápidos | Aceita qualquer erro, zero boilerplate |
thiserror | Bibliotecas | Tipos fortes, pattern matching, pouco boilerplate |
anyhow | Aplicações | Contexto rico, cadeia de erros, máxima simplicidade |
map_err | Conversão pontual | Quando só precisa converter em um ponto |
Regra geral: Use thiserror para bibliotecas (tipos de erro bem definidos) e anyhow para aplicações (simplicidade e contexto). Muitos projetos usam ambos: thiserror no domínio e anyhow na camada de aplicação.
Veja Também
- Tratar Erros em Rust — padrões básicos com match, ?, unwrap_or
- Tutorial: Tratamento de Erros — tutorial aprofundado sobre Result, Option e erros
- Glossário: Result — definição de Result e operador ?
- Glossário: From / Into — como o trait From possibilita conversão automática
- Rust Cheatsheet — referência rápida de erros e conversões
- Tratamento de erros em Go — compare a abordagem idiomática de Go para erros