Tratamento de Erros em Rust: try/catch e além — nível avançado
\n\nAo contrário de muitas linguagens onde exceções são o mecanismo padrão (como try/catch em Python, Java ou C++), Rust oferece uma abordagem fundamentalmente diferente. Aqui não existem exceções no sentido clássico. Em vez disso, a linguagem usa um poderoso sistema de tipos para representar e tratar erros. Para um iniciante, isso pode parecer complexo, mas em um nível avançado, você apreciará a segurança e previsibilidade que essa abordagem proporciona. Neste artigo, vamos explorar como trabalhar com erros em Rust profissionalmente: desde os básicos Result e Option até os crates avançados anyhow e thiserror, além de traçar paralelos com o familiar try/catch.
1. Fundamentos: Result, Option e o operador ?
\n\nA base do tratamento de erros em Rust é a enumeração Result<T, E>. Ela pode ser Ok(T) (sucesso) ou Err(E) (erro). O análogo ao try/catch pode ser considerado o operador ?. Ele simplifica o encaminhamento do erro para cima: se o valor for Err, a função o retorna imediatamente; se for Ok, extrai o valor.
use std::fs::File;\nuse std::io::Read;\n\nfn read_file(path: &str) -> Result<String, std::io::Error> {\n let mut file = File::open(path)?; // Análogo ao try: encaminhamento do erro\n let mut content = String::new();\n file.read_to_string(&mut content)?;\n Ok(content)\n}\n\nNeste exemplo, File::open e read_to_string podem retornar Err. O operador ? substitui o verboso match e torna o código limpo, como try/catch em outras linguagens. No entanto, ao contrário das exceções, você especifica explicitamente o tipo de erro na assinatura da função — isso aumenta a legibilidade e a segurança.
A enumeração Option<T> é usada para casos onde um valor pode estar ausente (análogo ao None em Python). O operador ? também funciona com ela:
fn get_first_element(vec: &Vec<i32>) -> Option<&i32> {\n vec.first()? // Se o vetor estiver vazio, retorna None\n}\n\n2. Trabalho avançado com tipos de erro: traits From e Error
\n\nQuando uma função chama várias operações com diferentes tipos de erro (por exemplo, entrada/saída e parsing), surge um problema: como retornar um tipo único? A solução é o trait From e um tipo de erro personalizado.
Vamos criar nossa própria estrutura de erro e implementar o trait std::error::Error para ela (automaticamente via #[derive(Debug)] e implementação manual de Display):
use std::fmt;\nuse std::io;\n\n#[derive(Debug)]\nenum MyError {\n Io(io::Error),\n Parse(std::num::ParseIntError),\n}\n\nimpl fmt::Display for MyError {\n fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n match self {\n MyError::Io(e) => write!(f, "Erro de IO: {}", e),\n MyError::Parse(e) => write!(f, "Erro de parsing: {}", e),\n }\n }\n}\n\nimpl std::error::Error for MyError {}\n\n// Implementamos From para conversão automática\nimpl From<io::Error> for MyError {\n fn from(error: io::Error) -> Self {\n MyError::Io(error)\n }\n}\n\nimpl From<std::num::ParseIntError> for MyError {\n fn from(error: std::num::ParseIntError) -> Self {\n MyError::Parse(error)\n }\n}\n\nfn process_data(path: &str) -> Result<i32, MyError> {\n let content = std::fs::read_to_string(path)?; // Converte automaticamente para MyError::Io\n let number: i32 = content.trim().parse()?; // Converte automaticamente para MyError::Parse\n Ok(number)\n}\n\nAgora a função process_data retorna um tipo único MyError, e o operador ? converte automaticamente os erros graças ao trait implementado