panic! en Rust: cuándo dejar que el programa falle y cuándo usar Result

Rust tiene dos mecanismos para los errores: Result para los errores que el llamador puede manejar, y panic! para los errores que no deberían ocurrir nunca. Saber cuándo usar cada uno es una de las primeras decisiones de diseño que tomas al escribir código Rust.

Qué es panic!

panic! detiene la ejecución del hilo actual de forma inmediata, imprime un mensaje y el stack trace, y el programa termina (o el hilo, si el panic ocurre en un hilo secundario).

fn main() {
    panic!("algo fue muy mal");
}
// thread 'main' panicked at 'algo fue muy mal', src/main.rs:2:5

La regla de decisión

SituaciónUsar
El error puede ocurrir en uso normal (red, fichero, input del usuario)Result
Invariante rota que indica un bug del programadorpanic!
Índice fuera de rango que no debería ocurrirpanic!
Prototipo / ejemplos donde el manejo de errores distraeunwrap/expect
Tests donde el fallo debe ser ruidosounwrap/expect

unwrap y expect: panic! implícito

// Hace panic si es Err o None
let n: i32 = "42".parse().unwrap();
let n: i32 = "42".parse().expect("El input debería ser un entero");

// expect con mensaje útil en producción para diagnosticar
let config_path = std::env::var("CONFIG")
    .expect("La variable CONFIG debe estar definida");

Prefiere expect(mensaje) sobre unwrap(): el mensaje aparece en el stack trace y ayuda a diagnosticar el fallo.

Casos donde panic! es correcto

Invariantes que no pueden romperse

fn dividir(a: f64, b: f64) -> f64 {
    assert!(b != 0.0, "El divisor no puede ser cero");
    a / b
}

// O más explícito:
fn indice_valido(slice: &[i32], i: usize) -> i32 {
    if i >= slice.len() {
        panic!("Índice {} fuera de rango para slice de longitud {}", i, slice.len());
    }
    slice[i]
}

Prototipos y ejemplos

// En un prototipo, unwrap está bien mientras desarrollas
fn main() {
    let contenido = std::fs::read_to_string("datos.txt").unwrap();
    // ... procesar contenido
}
// En producción, reemplaza por manejo real de errores

#[should_panic] en tests

#[cfg(test)]
mod tests {
    #[test]
    #[should_panic(expected = "división por cero")]
    fn test_division_por_cero() {
        dividir(10.0, 0.0);
    }
}

catch_unwind: capturar panics

En código de infraestructura (servidores, frameworks), a veces necesitas capturar un panic:

use std::panic;

let resultado = panic::catch_unwind(|| {
    panic!("algo falló");
});

match resultado {
    Ok(_)  => println!("Sin panic"),
    Err(_) => println!("Se capturó un panic"),
}

No abuses de catch_unwind: no es un sustituto del manejo normal de errores.

Modos de terminación

# Cargo.toml
[profile.release]
panic = "abort"  # termina inmediatamente sin unwind (binario más pequeño)

Por defecto, un panic hace stack unwinding (limpia memoria y ejecuta destructores). Con panic = "abort", el proceso termina inmediatamente. Útil en sistemas embebidos o cuando el tamaño del binario importa.

Resumen

  • panic! es para bugs del programador, no para errores de runtime esperados.
  • Usa Result cuando el llamador pueda recuperarse del error.
  • expect(msg) es mejor que unwrap(): el mensaje ayuda a diagnosticar.
  • #[should_panic] para testear que el código paniquea cuando debe.
  • catch_unwind para capturar panics en código de infraestructura.

El siguiente artículo cubre cómo crear tipos de error propios implementando los traits Display y std::error::Error, que es lo que necesitas para librerías con errores bien definidos.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP