Como Tratar Erros em Rust (Result e Option)

Receita prática de tratamento de erros em Rust: match, operador ?, unwrap_or, map_err e erros customizados. Código completo e executável.

Tratar erros corretamente é essencial para escrever software confiável. Rust usa os tipos Result<T, E> e Option<T> em vez de exceções, forçando você a lidar com erros de forma explícita. Nesta receita, você vai aprender os padrões mais comuns de tratamento de erros com código prático e executável.

Dependências

Nenhuma crate externa necessária:

[package]
name = "receita-erros"
version = "0.1.0"
edition = "2021"

Código Completo

use std::collections::HashMap;
use std::fmt;
use std::num::ParseIntError;

// =============================================
// Erro customizado
// =============================================
#[derive(Debug)]
enum AppErro {
    NaoEncontrado(String),
    Validacao(String),
    Parse(ParseIntError),
}

impl fmt::Display for AppErro {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            AppErro::NaoEncontrado(msg) => write!(f, "Não encontrado: {}", msg),
            AppErro::Validacao(msg) => write!(f, "Validação falhou: {}", msg),
            AppErro::Parse(e) => write!(f, "Erro de conversão: {}", e),
        }
    }
}

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

// =============================================
// Funções que retornam Result
// =============================================
fn buscar_usuario(id: u32) -> Result<String, AppErro> {
    let usuarios: HashMap<u32, &str> = HashMap::from([
        (1, "Ana"),
        (2, "Bia"),
        (3, "Carlos"),
    ]);

    usuarios
        .get(&id)
        .map(|nome| nome.to_string())
        .ok_or_else(|| AppErro::NaoEncontrado(format!("Usuário ID {}", id)))
}

fn validar_idade(texto: &str) -> Result<u32, AppErro> {
    let idade: u32 = texto.trim().parse()?; // converte ParseIntError via From

    if idade < 18 {
        return Err(AppErro::Validacao(
            format!("Idade {} é menor que 18", idade),
        ));
    }
    if idade > 130 {
        return Err(AppErro::Validacao(
            format!("Idade {} é inválida", idade),
        ));
    }

    Ok(idade)
}

fn processar_cadastro(id: u32, idade_texto: &str) -> Result<String, AppErro> {
    let nome = buscar_usuario(id)?;
    let idade = validar_idade(idade_texto)?;
    Ok(format!("{} ({} anos) cadastrado com sucesso", nome, idade))
}

// =============================================
// Funções que retornam Option
// =============================================
fn encontrar_extensao(arquivo: &str) -> Option<&str> {
    let ponto = arquivo.rfind('.')?;
    Some(&arquivo[ponto + 1..])
}

fn main() {
    println!("=== 1. Match com Result ===");
    match buscar_usuario(1) {
        Ok(nome) => println!("Encontrado: {}", nome),
        Err(e) => println!("Erro: {}", e),
    }
    match buscar_usuario(99) {
        Ok(nome) => println!("Encontrado: {}", nome),
        Err(e) => println!("Erro: {}", e),
    }

    println!("\n=== 2. Match com Option ===");
    let arquivos = vec!["foto.jpg", "documento.pdf", "README", "app.rs"];
    for arquivo in &arquivos {
        match encontrar_extensao(arquivo) {
            Some(ext) => println!("{} -> extensão: {}", arquivo, ext),
            None => println!("{} -> sem extensão", arquivo),
        }
    }

    println!("\n=== 3. Operador ? (propagação) ===");
    let testes = vec![
        (1, "25"),
        (2, "15"),
        (99, "30"),
        (3, "abc"),
    ];
    for (id, idade) in &testes {
        match processar_cadastro(*id, idade) {
            Ok(msg) => println!("OK: {}", msg),
            Err(e) => println!("ERRO: {}", e),
        }
    }

    println!("\n=== 4. unwrap_or e unwrap_or_else ===");
    let nome = buscar_usuario(99).unwrap_or("Desconhecido".to_string());
    println!("unwrap_or: {}", nome);

    let nome = buscar_usuario(99).unwrap_or_else(|e| {
        println!("  (tratando erro: {})", e);
        "Anônimo".to_string()
    });
    println!("unwrap_or_else: {}", nome);

    // unwrap_or_default usa o Default do tipo
    let valor: Result<i32, &str> = Err("falhou");
    println!("unwrap_or_default: {}", valor.unwrap_or_default()); // 0

    println!("\n=== 5. map e map_err ===");
    // map transforma o valor Ok
    let resultado = validar_idade("25").map(|idade| idade * 12);
    println!("Idade em meses: {:?}", resultado);

    // map_err transforma o erro
    let resultado = validar_idade("abc")
        .map_err(|e| format!("PROBLEMA: {}", e));
    println!("map_err: {:?}", resultado);

    // and_then encadeia operações que retornam Result
    let resultado = validar_idade("25")
        .and_then(|idade| {
            if idade >= 21 {
                Ok(format!("{} anos - pode tudo!", idade))
            } else {
                Err(AppErro::Validacao("Menor de 21".into()))
            }
        });
    println!("and_then: {:?}", resultado);

    println!("\n=== 6. Convertendo Option <-> Result ===");
    // Option -> Result com ok_or
    let opt: Option<i32> = Some(42);
    let res: Result<i32, &str> = opt.ok_or("valor ausente");
    println!("Option -> Result: {:?}", res);

    let opt: Option<i32> = None;
    let res: Result<i32, &str> = opt.ok_or("valor ausente");
    println!("None -> Result:   {:?}", res);

    // Result -> Option com ok()
    let res: Result<i32, &str> = Ok(42);
    let opt: Option<i32> = res.ok();
    println!("Result -> Option: {:?}", opt);

    println!("\n=== 7. Coletando Results de iterador ===");
    let textos = vec!["1", "2", "3", "4"];
    let numeros: Result<Vec<i32>, _> = textos
        .iter()
        .map(|t| t.parse::<i32>())
        .collect();
    println!("Todos válidos: {:?}", numeros);

    let textos = vec!["1", "abc", "3"];
    let numeros: Result<Vec<i32>, _> = textos
        .iter()
        .map(|t| t.parse::<i32>())
        .collect();
    println!("Com inválido:  {:?}", numeros);

    println!("\n=== 8. if let e let else ===");
    // if let — executar código apenas se Ok/Some
    if let Ok(nome) = buscar_usuario(1) {
        println!("if let: Olá, {}!", nome);
    }

    // let else — retornar cedo se Err/None
    fn saudar(id: u32) {
        let Ok(nome) = buscar_usuario(id) else {
            println!("let else: Usuário {} não encontrado", id);
            return;
        };
        println!("let else: Bem-vindo, {}!", nome);
    }
    saudar(2);
    saudar(99);
}

Saída do Programa

=== 1. Match com Result ===
Encontrado: Ana
Erro: Não encontrado: Usuário ID 99

=== 2. Match com Option ===
foto.jpg -> extensão: jpg
documento.pdf -> extensão: pdf
README -> sem extensão
app.rs -> extensão: rs

=== 3. Operador ? (propagação) ===
OK: Ana (25 anos) cadastrado com sucesso
ERRO: Validação falhou: Idade 15 é menor que 18
ERRO: Não encontrado: Usuário ID 99
ERRO: Erro de conversão: invalid digit found in string

=== 4. unwrap_or e unwrap_or_else ===
unwrap_or: Desconhecido
  (tratando erro: Não encontrado: Usuário ID 99)
unwrap_or_else: Anônimo
unwrap_or_default: 0

=== 5. map e map_err ===
Idade em meses: Ok(300)
map_err: Err("PROBLEMA: Erro de conversão: invalid digit found in string")
and_then: Ok("25 anos - pode tudo!")

=== 6. Convertendo Option <-> Result ===
Option -> Result: Ok(42)
None -> Result:   Err("valor ausente")
Result -> Option: Some(42)

=== 7. Coletando Results de iterador ===
Todos válidos: Ok([1, 2, 3, 4])
Com inválido:  Err(ParseIntError { kind: InvalidDigit })

=== 8. if let e let else ===
if let: Olá, Ana!
let else: Bem-vindo, Bia!
let else: Usuário 99 não encontrado

Resumo dos Padrões

PadrãoQuando usar
matchTratar cada caso explicitamente
?Propagar erro para a função chamadora
unwrap_or(v)Fornecer valor padrão fixo
unwrap_or_else(f)Calcular valor padrão com closure
map(f)Transformar o valor Ok/Some
map_err(f)Transformar o tipo de erro
and_then(f)Encadear operações que podem falhar
if letExecutar código apenas se sucesso
let elseRetornar cedo se falha

Veja Também