Manejo de errores en Rust: try/catch y más — nivel avanzado
\n\nA diferencia de muchos lenguajes donde las excepciones son el mecanismo estándar (como try/catch en Python, Java o C++), Rust ofrece un enfoque fundamentalmente diferente. Aquí no hay excepciones en el sentido clásico. En su lugar, el lenguaje utiliza un potente sistema de tipos para representar y manejar errores. Para un principiante esto puede parecer complejo, pero en un nivel avanzado apreciarás la seguridad y previsibilidad que ofrece este enfoque. En este artículo, analizaremos cómo trabajar con errores en Rust de manera profesional: desde los básicos Result y Option hasta los avanzados crates anyhow y thiserror, además de establecer paralelismos con el conocido try/catch.
1. Fundamento: Result, Option y el operador ?
\n\nLa base del manejo de errores en Rust es la enumeración Result<T, E>. Puede ser Ok(T) (éxito) o Err(E) (error). Un análogo de try/catch sería el operador ?. Este simplifica la propagación del error hacia arriba: si el valor es Err, la función lo retorna inmediatamente; si es Ok, extrae el 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 a try: propagación del error\n let mut content = String::new();\n file.read_to_string(&mut content)?;\n Ok(content)\n}\n\nEn este ejemplo, File::open y read_to_string pueden devolver Err. El operador ? reemplaza el engorroso match y hace que el código sea limpio, como try/catch en otros lenguajes. Sin embargo, a diferencia de las excepciones, indicas explícitamente el tipo de error en la firma de la función, lo que mejora la legibilidad y la seguridad.
La enumeración Option<T> se utiliza para casos donde un valor puede estar ausente (análogo a None en Python). El operador ? también funciona con ella:
fn get_first_element(vec: &Vec<i32>) -> Option<&i32> {\n vec.first()? // Si el vector está vacío, retornamos None\n}\n\n2. Trabajo avanzado con tipos de error: traits From y Error
\n\nCuando una función llama a varias operaciones con diferentes tipos de error (por ejemplo, entrada/salida y análisis sintáctico), surge un problema: ¿cómo devolver un tipo único? La solución son el trait From y un tipo de error personalizado.
Creemos nuestra propia estructura de error e implementemos para ella el trait std::error::Error (automáticamente mediante #[derive(Debug)] e implementación 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, "Error de E/S: {}", e),\n MyError::Parse(e) => write!(f, "Error de análisis: {}", e),\n }\n }\n}\n\nimpl std::error::Error for MyError {}\n\n// Implementamos From para la conversión 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)?; // Se convierte automáticamente a MyError::Io\n let number: i32 = content.trim().parse()?; // Se convierte automáticamente a MyError::Parse\n Ok(number)\n}\n\nAhora la función process_data devuelve un tipo único MyError, y el operador ? convierte automáticamente los errores gracias al trait From implementado.