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ão | Quando usar |
|---|---|
match | Tratar 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 let | Executar código apenas se sucesso |
let else | Retornar cedo se falha |
Veja Também
- Tutorial: Tratamento de Erros em Rust — tutorial completo com thiserror e anyhow
- Converter Entre Tipos de Erro — From trait, thiserror e anyhow
- Rust Cheatsheet — referência rápida de Result e Option
- Glossário: Result — definição e exemplos de Result
- Glossário: Option — definição e exemplos de Option
- Tratamento de erros em Go — compare com a abordagem de tratamento de erros em Go