O Tipo Result em Rust
Result é o tipo fundamental de Rust para tratamento de erros, diferente completamente do modelo de exceções. Ele é um enum que representa dois estados: sucesso (Ok(T)) ou falha (Err(E)). A principal vantagem é forçar o programador a lidar com possibilidades de erro em tempo de compilação, eliminando a surpresa de exceções não tratadas que vemos em linguagens como Java ou Python.
enum Result<T, E> {
Ok(T),
Err(E),
}
Todo Result deve ser manipulado explicitamente. Ignorar um Result gera um aviso do compilador, garantindo que você nunca "deixe passar" um erro involuntariamente. Isso torna o código mais robusto e previsível desde o início do desenvolvimento.
Padrões de Tratamento Básicos
Match e Pattern Matching
O match é a forma mais explícita de lidar com Result. Você obriga o código a considerar ambos os casos:
use std::fs::File;
fn ler_arquivo(caminho: &str) -> Result<String, std::io::Error> {
let mut arquivo = File::open(caminho)?;
let mut conteudo = String::new();
arquivo.read_to_string(&mut conteudo)?;
Ok(conteudo)
}
fn main() {
match ler_arquivo("dados.txt") {
Ok(conteudo) => println!("Lido: {}", conteudo),
Err(erro) => eprintln!("Erro: {}", erro),
}
}
O match é verbose mas extremamente claro. Cada branch deve retornar o mesmo tipo, o que incentiva tratamento coeso de erros.
Operador ? (Propagação)
O ponto de interrogação é açúcar sintático que propaga erros automaticamente. Se uma operação falha, o erro é retornado imediatamente da função atual:
fn processar_dados() -> Result<i32, Box<dyn std::error::Error>> {
let arquivo = File::open("dados.txt")?;
let numero: i32 = std::fs::read_to_string("numero.txt")?
.trim()
.parse()?;
Ok(numero * 2)
}
O ? só funciona em funções que retornam Result. Para main(), use .unwrap_or_else() ou retorne Result do main (Rust 1.26+).
Métodos Essenciais de Result
Result fornece métodos combinadores que permitem transformar, filtrar e processar valores sem destruir a estrutura do tipo:
fn main() {
let resultado: Result<i32, String> = Ok(5);
// map: transformar o valor Ok
let dobrado = resultado.map(|x| x * 2); // Ok(10)
// map_err: transformar o erro
let erro_msg = Err::<i32, &str>("falha").map_err(|e| format!("Erro: {}", e));
// unwrap_or: valor padrão em caso de erro
let valor = Err::<i32, String>("problema".to_string()).unwrap_or(0); // 0
// and_then: acorrentar operações que retornam Result
let resultado = Ok(10)
.and_then(|x| if x > 5 { Ok(x * 2) } else { Err("muito pequeno") });
// or_else: oferecer alternativa em caso de erro
let fallback = Err::<i32, &str>("erro1")
.or_else(|_| Ok::<i32, &str>(99)); // Ok(99)
}
Esses combinadores eliminam a necessidade de múltiplos match aninhados, tornando o código funcional e elegante.
Erros Customizados e Boas Práticas
Definindo Tipos de Erro Próprios
Para aplicações reais, crie tipos de erro específicos que implementem std::error::Error:
use std::error::Error;
use std::fmt;
#[derive(Debug)]
enum ErroArquivo {
NaoEncontrado(String),
PermissaoNegada,
FormatoInvalido(String),
}
impl fmt::Display for ErroArquivo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ErroArquivo::NaoEncontrado(nome) =>
write!(f, "Arquivo não encontrado: {}", nome),
ErroArquivo::PermissaoNegada =>
write!(f, "Permissão negada ao acessar arquivo"),
ErroArquivo::FormatoInvalido(motivo) =>
write!(f, "Formato inválido: {}", motivo),
}
}
}
impl Error for ErroArquivo {}
fn validar_json(conteudo: &str) -> Result<(), ErroArquivo> {
if conteudo.is_empty() {
return Err(ErroArquivo::FormatoInvalido("JSON vazio".into()));
}
Ok(())
}
Tipos de erro customizados permitem que quem chama sua função entenda exatamente quais falhas são possíveis e trate cada uma apropriadamente.
Crate anyhow para Contexto
Em projetos maiores, use a crate anyhow para adicionar contexto aos erros sem criar tipos complexos:
// Adicione ao Cargo.toml: anyhow = "1.0"
use anyhow::{Context, Result};
fn ler_config() -> Result<String> {
let conteudo = std::fs::read_to_string("config.toml")
.context("Falha ao ler arquivo de configuração")?;
Ok(conteudo)
}
fn main() {
match ler_config() {
Ok(config) => println!("Config carregada"),
Err(e) => eprintln!("Erro: {:?}", e),
}
}
O anyhow::Result é equivalente a Result<T, Box<dyn Error>>, oferecendo flexibilidade com contexto.
Conclusão
Resultmap, and_then e ? tornam o tratamento elegante e funcional; (3) erros customizados e crates como anyhow escalam bem em projetos reais.
Domine esses padrões e você escreverá código Rust robusto, previsível e mantível.