Como Converter Entre Tipos de Erro em Rust

Aprenda a converter entre tipos de erro em Rust com From trait, thiserror, anyhow e Box<dyn Error>. Guia completo com exemplos práticos e executáveis.

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

AbordagemIdeal paraVantagens
From manualAprendizado, controle totalTotal controle sobre conversão
Box<dyn Error>Protótipos rápidosAceita qualquer erro, zero boilerplate
thiserrorBibliotecasTipos fortes, pattern matching, pouco boilerplate
anyhowAplicaçõesContexto rico, cadeia de erros, máxima simplicidade
map_errConversão pontualQuando 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