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 un Result. El tipo genérico T es f64 (éxito) y el tipo E es String (error).
  • Err(String::from(...)): Crea una instancia de la variante Err para indicar que la operación no pudo completarse debido al denominador cero.
  • Ok(numerador / denominador): Envuelve el resultado matemático exitoso en la variante Ok.
  • match division_1: Evalúa la variable division_1. Si es Ok, extrae el valor en la variable resultado; si es Err, extrae el texto en la variable mensaje.
  • division_2.unwrap_or(0.0): Este método intenta extraer el valor de division_2. Si es Ok, devuelve el número; si es Err, devuelve el valor 0.0 proporcionado.
  • division_3.expect("..."): Similar a unwrap, pero si division_3 es un Err, el programa se detiene inmediatamente (panic) mostrando el mensaje personalizado proporcionado.

58