21 - Manejo de errores en Rust - result y panic

21 - Manejo de errores en Rust - result y panic

Manejo de errores en Rust: result y panic #

En Rust, el manejo de errores es una parte fundamental del desarrollo seguro y confiable. A diferencia de otros lenguajes que pueden propagar errores en tiempo de ejecución sin previo aviso, Rust nos obliga a manejar los errores de forma explícita. En este artículo, exploraremos las dos principales estrategias para manejar errores en Rust: Result y panic!, con ejemplos prácticos basados en el mundo Pokémon.


Panic!: terminación inmediata del programa #

La macro panic! se usa cuando un error es irrecuperable y el programa no puede continuar su ejecución. Al invocarla, Rust imprimirá un mensaje de error y abortará el programa.

Ejemplo: intentar capturar un pokémon sin poké balls #

fn capturar_elemento(pokeballs: u8) {
    if pokeballs == 0 {
        panic!("¡No tienes PokeBalls! No puedes capturar Pokémon.");
    } else {
        println!("Lanzas una Poké Ball... ¡Captura exitosa!");
    }
}

fn main() {
    let pokeballs = 0;
    capturar_elemento(pokeballs);
}

Salida esperada:

thread 'main' panicked at '¡No tienes PokeBalls! No puedes capturar Pokémon.', src/main.rs:4:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

📌 Cuándo usar panic!:

  • Cuando un error indica un estado irrecuperable (ejemplo: corrupción de memoria).
  • En código prototipado donde aún no se ha implementado un manejo de errores adecuado.
  • En situaciones donde fallar rápido es preferible a continuar con un estado inconsistente.

Result<t, e>: manejo seguro de errores #

Rust proporciona el tipo Result<T, E> para manejar errores de manera controlada sin detener el programa.

enum Result<T, E> {
    Ok(T),
    Err(E),
}

Esto permite que una función retorne un Ok(valor) en caso de éxito o un Err(error) en caso de fallo.

Ejemplo: verificar si un pokémon puede aprender un movimiento #

enum ErrorAprendizaje {
    NoTieneNivelSuficiente,
    MovimientoNoCompatible,
}

fn aprender_movimiento(nivel: u8, compatible: bool) -> Result<&'static str, ErrorAprendizaje> {
    if nivel < 15 {
        return Err(ErrorAprendizaje::NoTieneNivelSuficiente);
    }
    if !compatible {
        return Err(ErrorAprendizaje::MovimientoNoCompatible);
    }
    Ok("¡Movimiento aprendido con éxito!")
}

fn main() {
    let resultado = aprender_movimiento(10, true);
    match resultado {
        Ok(mensaje) => println!("{}", mensaje),
        Err(ErrorAprendizaje::NoTieneNivelSuficiente) => println!("El Pokémon necesita subir de nivel."),
        Err(ErrorAprendizaje::MovimientoNoCompatible) => println!("El Pokémon no puede aprender este movimiento."),
    }
}

Salida esperada:

El Pokémon necesita subir de nivel.

📌 Cuándo usar Result<T, E>:

  • Para manejar fallos que pueden ser recuperables.
  • Al interactuar con operaciones externas como archivos, redes o bases de datos.
  • Cuando se desea diferenciar entre éxito (Ok) y error (Err).

Propagación de errores con ? #

El operador ? permite propagar errores automáticamente sin necesidad de usar match explícito.

Ejemplo: leer datos de un archivo con información de un pokémon #

use std::fs::File;
use std::io::{self, Read};

fn leer_elemento_desde_archivo(nombre: &str) -> Result<String, io::Error> {
    let mut archivo = File::open(nombre)?;
    let mut contenido = String::new();
    archivo.read_to_string(&mut contenido)?;
    Ok(contenido)
}

fn main() {
    match leer_elemento_desde_archivo("pokedex.txt") {
        Ok(datos) => println!("Datos del Pokémon: {}", datos),
        Err(e) => println!("Error al leer el archivo: {}", e),
    }
}

📌 Cuándo usar ?:

  • Cuando una función retorna Result<T, E> y queremos propagar errores sin escribir match explícito.
  • Para hacer el código más limpio y legible.

Evitando unwrap() y expect() en producción #

Métodos como .unwrap() y .expect() pueden causar panic! si un Result es Err.

let archivo = File::open("pokedex.txt").unwrap(); // Panics si el archivo no existe

En producción, es mejor manejar errores correctamente con match o ? en lugar de unwrap().

let archivo = File::open("pokedex.txt").unwrap_or_else(|_| {
    println!("No se pudo abrir el archivo, creando uno nuevo.");
    File::create("pokedex.txt").expect("Error al crear el archivo")
});

Conclusión #

Rust proporciona mecanismos seguros y explícitos para manejar errores:

  • panic! se usa para fallos críticos e irrecuperables.
  • Result<T, E> permite manejar errores de manera controlada.
  • ? simplifica la propagación de errores.
  • Evitar unwrap() y expect() en código de producción ayuda a mejorar la estabilidad del programa.

Aplicar estas estrategias correctamente permite escribir código más seguro, confiable y resistente a fallos.