En Rust, los errores no se “lanzan” como excepciones en otros lenguajes. En su lugar, los errores son valores que se devuelven como parte del flujo normal del programa. Esto se logra mediante el tipo Result<T, E>, un enum que representa un resultado que puede ser de dos tipos: Ok(T), que contiene el valor en caso de éxito, o Err(E), que contiene la información del error.
Esta aproximación cambia radicalmente cómo se programa: en lugar de esperar que una función lance una excepción inesperada que detenga el programa, la firma de la función te obliga a declarar explícitamente que algo puede fallar. Si una función devuelve un Result, el compilador te exigirá que manejes tanto el caso de éxito como el de error. Esto hace que el código sea mucho más robusto y predecible.
Existen dos categorías de errores en Rust: los recuperables (usando Result), donde el programa puede tomar una decisión para continuar, y los no recuperables (usando panic!), que se utilizan para errores fatales o bugs lógicos donde el programa simplemente debe detenerse.
Para manejar los Result, puedes usar estructuras de control como match para desestructurar el valor, o métodos de conveniencia como unwrap() (que provoca un pánico si el resultado es Err), expect() (que permite personalizar el mensaje de pánico), o unwrap_or() (que proporciona un valor por defecto si hay un error).
// Función que realiza una división y devuelve un Result
// El tipo T es f64 (el número) y el tipo E es String (el mensaje de error)
fn dividir(numerador: f64, denominador: f64) -> Result<f64, String> {
if denominador == 0.0 {
// Retornamos la variante Err si hay división por cero
Err(String::from("Error: No se puede dividir por cero"))
} else {
// Retornamos la variante Ok con el resultado
Ok(numerador / denominador)
}
}
fn main() {
let num1 = 10.0;
let num2 = 2.0;
let num3 = 0.0;
// 1. Uso de match para un manejo seguro y explícito
let division_1 = dividir(num1, num2);
match division_1 {
Ok(resultado) => println!("Resultado exitoso: {}", resultado),
Err(mensaje) => println!("Error encontrado: {}", mensaje),
}
// 2. Uso de un valor por defecto con unwrap_or
// Si la división falla, se usará 0.0 en lugar de un error
let division_2 = dividir(num1, num3);
let valor_seguro = division_2.unwrap_or(0.0);
println!("Resultado con valor por defecto: {}", valor_seguro);
// 3. Uso de expect para fallar con un mensaje personalizado
// Se usa cuando estamos seguros de que no fallará o es un error crítico
let division_3 = dividir(num1, num2).expect("La división debería ser posible");
println!("Resultado con expect: {}", division_3);
}
Explicación del Código
fn dividir(numerador: f64, denominador: f64) -> Result<f64, String>: Define una función que devuelve unResult. El tipo genéricoTesf64(éxito) y el tipoEesString(error).Err(String::from(...)): Crea una instancia de la varianteErrpara indicar que la operación no pudo completarse debido al denominador cero.Ok(numerador / denominador): Envuelve el resultado matemático exitoso en la varianteOk.match division_1: Evalúa la variabledivision_1. Si esOk, extrae el valor en la variableresultado; si esErr, extrae el texto en la variablemensaje.division_2.unwrap_or(0.0): Este método intenta extraer el valor dedivision_2. Si esOk, devuelve el número; si esErr, devuelve el valor0.0proporcionado.division_3.expect("..."): Similar aunwrap, pero sidivision_3es unErr, el programa se detiene inmediatamente (panic) mostrando el mensaje personalizado proporcionado.
N° 58