Tratamento de erros em Rust: abandonando try/catch em prol da segurança
Ao contrário de Python ou Java, Rust não possui a construção clássica try/catch. Os desenvolvedores da linguagem seguiram outro caminho, tornando o tratamento de erros parte do sistema de tipos. Isso não é um capricho, mas uma decisão fundamental que permite garantir a segurança da memória e a previsibilidade da execução do programa em tempo de compilação. Neste artigo, abordaremos todas as formas de tratamento de erros em Rust: desde os básicos Result e Option até métodos avançados com o operador ? e tipos de erro personalizados.
1. Dois tipos principais: Result e Option
Em Rust, todos os erros são divididos em duas categorias: recuperáveis (tratados através de Result) e irrecuperáveis (que levam a panic!). Existe também o tipo Option para casos em que um valor pode estar ausente.
Tipo Result
Result<T, E> é uma enumeração (enum) que pode ser Ok(T) (sucesso com dados) ou Err(E) (erro com descrição). Esta é a ferramenta principal para tratar erros esperados: erros de entrada/saída, parsing, requisições de rede.
use std::fs::File;use std::io::Read;
fn read_file_content(path: &str) -> Result<String, std::io::Error> { let mut file = File::open(path)?; let mut content = String::new(); file.read_to_string(&mut content)?; Ok(content)}Tipo Option
Option<T> é outro enum que assume os valores Some(T) (valor existe) ou None (valor não existe). É usado quando a ausência de dados é uma situação normal, e não um erro.
fn find_user(id: u32) -> Option<String> { let users = vec!["Alice".to_string(), "Bob".to_string()]; users.get(id as usize).cloned()}
fn main() { match find_user(0) { Some(name) => println!("Usuário encontrado: {}", name), None => println!("Usuário não encontrado"), }}2. Operador ? — substituição elegante para try/catch
Se Rust tivesse try/catch, ele seria verboso. Em vez disso, a linguagem oferece o operador ?. Ele funciona assim: se o resultado for Ok, o valor é extraído e passado adiante; se for Err, o erro é imediatamente retornado da função.
use std::num::ParseIntError;
fn parse_and_double(input: &str) -> Result<i32, ParseIntError> { let value: i32 = input.parse()?; // se erro — retorna Err imediatamente Ok(value * 2)}
fn main() { match parse_and_double("42") { Ok(result) => println!("Resultado: {}", result), Err(e) => println!("Erro de parsing: {}", e), }}O operador ? só pode ser usado em funções que retornam Result ou Option. Isso força o desenvolvedor a indicar explicitamente que a função pode falhar — ao contrário das exceções em Python, que podem "surgir" de qualquer lugar.
3. Quando usar panic! e unwrap
Nem todos os erros precisam ser tratados. panic! é uma macro para erros irrecuperáveis. Ela interrompe o programa e desenrola a pilha (stack unwind). Use-a apenas em situações excepcionais:
- Estados críticos: violação de invariantes que não podem ser corrigidos.
- Exemplos e protótipos: em código educacional ou scripts descartáveis.
- Testes: para verificar se o código falha nos lugares esperados.
fn divide(a: i32, b: i32) -> i32 { if b == 0 { panic!("Divisão por zero! a={}, b={}", a, b); } a / b}unwrap() e expect()