En Java o Python, una función puede lanzar una excepción en cualquier momento y el compilador no te obliga a manejarla. En Rust, los errores esperados se representan con Result<T, E>: un enum que el compilador te obliga a inspeccionar. Si una función puede fallar, su tipo de retorno lo dice explícitamente. No hay excepciones ocultas.
La definición de Result
enum Result<T, E> {
Ok(T), // operación exitosa, contiene el valor T
Err(E), // operación fallida, contiene el error E
}
Ejemplo: leer un fichero
use std::fs;
use std::io;
fn leer_fichero(ruta: &str) -> Result<String, io::Error> {
fs::read_to_string(ruta)
}
fn main() {
match leer_fichero("datos.txt") {
Ok(contenido) => println!("Contenido:n{}", contenido),
Err(e) => println!("Error al leer: {}", e),
}
}
El tipo de retorno Result<String, io::Error> indica que puede devolver un String o un io::Error. El llamador sabe que puede fallar antes de leer una sola línea de implementación.
match para manejar ambos casos
fn parsear_numero(s: &str) -> Result<i32, std::num::ParseIntError> {
s.trim().parse()
}
fn main() {
let resultado = parsear_numero("42");
match resultado {
Ok(n) => println!("Número: {}", n),
Err(e) => println!("Error: {}", e),
}
}
unwrap y expect
Si estás seguro de que el resultado es Ok, puedes extraer el valor con unwrap() o expect():
// Hace panic si es Err
let n: i32 = "42".parse().unwrap();
// Hace panic con mensaje personalizado
let n: i32 = "42".parse().expect("Debería ser un número válido");
En producción, evita unwrap() salvo en tests o cuando tengas certeza lógica absoluta.
Métodos de Result
let r: Result<i32, &str> = Ok(10);
// unwrap_or: valor por defecto si Err
let v = r.unwrap_or(0);
// map: transforma el Ok
let doble = r.map(|n| n * 2); // Ok(20)
// map_err: transforma el Err
let r2: Result<i32, String> = r.map_err(|e| format!("Error: {}", e));
// and_then: encadena operaciones que pueden fallar
let resultado = "42".parse::<i32>()
.and_then(|n| if n > 0 { Ok(n * 2) } else { Err("número negativo".parse().unwrap()) });
Múltiples tipos de error
use std::num::ParseIntError;
use std::io;
fn leer_numero_de_fichero(ruta: &str) -> Result<i32, Box<dyn std::error::Error>> {
let contenido = fs::read_to_string(ruta)?;
let numero: i32 = contenido.trim().parse()?;
Ok(numero)
}
Box<dyn std::error::Error> es el tipo de error comodín que acepta cualquier error. Perfecto para aplicaciones donde no necesitas distinguir el tipo exacto del error.
Comparativa con try/catch
| Aspecto | Java/Python (excepciones) | Rust (Result) |
|---|---|---|
| Error visible en firma | Solo con checked exceptions (Java) | Siempre |
| Olvidar manejar el error | Posible (runtime crash) | Warning del compilador |
| Control de flujo oculto | Sí (throw puede venir de cualquier lado) | No |
| Coste en runtime | Stack unwinding (caro) | Cero overhead (es un enum) |
Resumen
Result<T, E>es un enum conOk(T)yErr(E).- El compilador te obliga a inspeccionar el resultado.
- Métodos útiles:
map,map_err,and_then,unwrap_or. - Para múltiples tipos de error:
Box<dyn Error>. - El operador
?propaga errores sin escribir un match en cada línea.
El siguiente artículo se centra en el operador ?: cómo funciona internamente, cómo convierte tipos de error y por qué simplifica tanto el código de manejo de errores en Rust.
