Panic en Rust
Un panic (pánico) es la forma que tiene Rust de detener un programa en el acto cuando algo ha salido mal sin remedio. Si has seguido las páginas anteriores, seguramente ya has provocado alguno sin querer: acceder a una posición más allá del final de un array o llamar a .unwrap() sobre un None provocan un panic.
Provocar un panic a propósito
Sección titulada «Provocar un panic a propósito»fn main() { panic!("algo salió muy mal");}thread 'main' (12345) panicked at src/main.rs:2:5:algo salió muy malnote: run with `RUST_BACKTRACE=1` environment variable to display a backtraceEl número entre paréntesis, aquí (12345), es el ID del hilo. Verás un número distinto en cada ejecución, y las versiones antiguas de Rust no lo muestran.
Al ejecutarlo, el programa muestra ese mensaje y se detiene: nada de lo que va después del panic! se ejecuta. El resto del texto lo escribe Rust y siempre sale en inglés: thread 'main' (12345) panicked at src/main.rs:2:5 indica el archivo, la línea y la columna donde ocurrió el panic, y la nota final te dice que puedes activar la variable de entorno RUST_BACKTRACE=1 para ver el recorrido completo de llamadas (el backtrace).
Cosas que provocan un panic sin que llames a panic!
Sección titulada «Cosas que provocan un panic sin que llames a panic!»Algunas operaciones que ya conoces provocan un panic por su cuenta si algo va mal. En cada ejemplo, el valor peligroso llega a través de un parámetro de función en lugar de estar escrito directamente en el código. Si pusieras el índice o el 0 tal cual, el compilador muchas veces se daría cuenta de que la operación siempre falla y se negaría a compilar, en vez de dejar que el programa se ejecute y haga panic.
fn get(numbers: &[i32], index: usize) -> i32 { numbers[index] // panic: índice fuera de rango}
fn main() { let numbers = [1, 2, 3]; println!("{}", get(&numbers, 10));}fn main() { let value: Option<i32> = None; println!("{}", value.unwrap()); // panic: called `Option::unwrap()` on a `None` value}fn divide(a: i32, b: i32) -> i32 { a / b // panic: attempt to divide by zero}
fn main() { let a = 10; let b = 0; println!("{}", divide(a, b));}Los mensajes de los comentarios son los que imprime Rust, en inglés: “se llamó a Option::unwrap() sobre un valor None” e “intento de dividir entre cero”. En todos estos casos, Rust se niega a dejar que tu programa siga en un estado que no tiene sentido, en lugar de devolver basura sin avisar.
panic! o Result
Sección titulada «panic! o Result»Esta es una decisión que tomarás a menudo: ¿este fallo debe detener el programa, o quien llama a la función debe tener la oportunidad de manejarlo?
panic!: para bugs y situaciones que nunca deberían ocurrir si el código es correcto. No hay una forma razonable de recuperarse, así que parar en el acto (antes de causar más daño) es lo más seguro.Result(lo viste en la página anterior): para fallos que quien llama puede esperar y querer manejar, como una entrada incorrecta del usuario, un archivo que no existe o una petición de red que falla.
// Entrada incorrecta de un usuario: se puede recuperar, usa Resultfn parse_age(input: &str) -> Result<u32, String> { input.parse().map_err(|_| String::from("no es una edad válida"))}
// Algo que debería ser imposible si el código es correcto: panicfn get_first(numbers: &[i32]) -> i32 { if numbers.is_empty() { panic!("se llamó a get_first con un slice vacío"); } numbers[0]}unwrap() y expect() son atajos para provocar un panic
Sección titulada «unwrap() y expect() son atajos para provocar un panic»Ya has visto .unwrap() tanto en Option como en Result. En realidad es un atajo para decir “dame el valor, o haz panic si no hay ninguno”. .expect("mensaje") hace lo mismo, pero te deja añadir un mensaje más útil:
fn main() { let input = "42"; let number: i32 = input.parse().expect("la entrada debería ser un número válido"); println!("{number}");}Si la conversión falla, el mensaje del panic incluye tu texto, y eso hace que encontrar el problema sea mucho más fácil que con el mensaje genérico de .unwrap().
Comprobar tus suposiciones: assert!, assert_eq! y unreachable!
Sección titulada «Comprobar tus suposiciones: assert!, assert_eq! y unreachable!»A veces sabes que algo tiene que ser cierto en un punto concreto de tu código. Rust tiene unas cuantas macros pequeñas que convierten esa creencia en una comprobación. Si la comprobación falla, hacen panic con un mensaje claro, así que el bug aparece justo donde empieza en lugar de causar resultados raros más adelante.
assert!(condición, "mensaje")hace panic si la condición esfalse.assert_eq!(izquierda, derecha)hace panic si los dos valores no son iguales, y muestra ambos.unreachable!("mensaje")marca código que nunca debería ejecutarse, como una rama dematchpara un valor que no puede darse.
fn average(numbers: &[i32]) -> i32 { assert!(!numbers.is_empty(), "average necesita al menos un número");
let mut total = 0; for n in numbers { total += n; } total / numbers.len() as i32}
fn day_name(day: u8) -> &'static str { match day { 1 => "lunes", 2 => "martes", 3 => "miércoles", 4 => "jueves", 5 => "viernes", 6 => "sábado", 7 => "domingo", _ => unreachable!("el día debe estar entre 1 y 7, pero es {day}"), }}
fn main() { let avg = average(&[70, 80, 90]); assert_eq!(avg, 80); println!("media: {avg}");
println!("el día 3 es {}", day_name(3));}Salida:
media: 80el día 3 es miércolesAquí todas las comprobaciones se cumplen, así que el programa funciona con normalidad. Ahora cambia la lista a [70, 80, 95] en el Playground. La media pasa a ser 81, y assert_eq! detiene el programa mostrando los dos valores, uno al lado del otro:
thread 'main' (12345) panicked at src/main.rs:26:5:assertion `left == right` failed left: 81 right: 80Este mensaje lo escribe Rust en inglés: dice que la aserción left == right (izquierda igual a derecha) falló, y muestra el valor de la izquierda (81) y el de la derecha (80). Prueba también average(&[]) o day_name(9) y verás tus propios mensajes en el panic.
Estas comprobaciones se ejecutan siempre, incluso en una compilación release, así que procura que sean baratas. Volverás a ver assert_eq! en la página de Testing, donde es la forma principal que tiene un test de decir “esto debería ser igual a aquello”. También existe todo!(), que hace panic con el mensaje “not yet implemented” (“todavía no implementado”). Es muy útil como cuerpo provisional mientras esbozas una función que terminarás más tarde.
Práctica
Sección titulada «Práctica»1. Un número de puerto con expect
Sección titulada «1. Un número de puerto con expect»Tienes un valor de configuración let port_text = "8080";. Conviértelo en un u16 con .parse() y .expect(...), usando un mensaje que explique qué salió mal. Después imprime Escuchando en el puerto 8080. Pista: la variable necesita un tipo, let port: u16 = ..., para que parse sepa qué tiene que producir. Luego cambia el texto a "80a0" para ver tu mensaje en el panic.
Ver solución
fn main() { let port_text = "8080"; let port: u16 = port_text .parse() .expect("el puerto de la configuración debería ser un número entre 0 y 65535"); println!("Escuchando en el puerto {port}");}Salida:
Escuchando en el puerto 80802. Dividir la cuenta
Sección titulada «2. Dividir la cuenta»Escribe fn split_bill(total: u32, people: u32) -> u32, que devuelva lo que le toca pagar a cada persona. Dividir entre cero personas es un bug en el código que llama a la función, así que usa assert! con un mensaje claro para detectarlo. Imprime la parte de cada uno para un total de 120 y 4 personas. Pista: pon el assert! en la primera línea de la función, antes de la división.
Ver solución
fn split_bill(total: u32, people: u32) -> u32 { assert!(people > 0, "split_bill necesita al menos una persona"); total / people}
fn main() { let share = split_bill(120, 4); println!("Cada persona paga {share}");}Salida:
Cada persona paga 30Resumen
Sección titulada «Resumen»panic!detiene el programa en el acto con un mensaje de error.- Algunas operaciones estándar (un índice fuera de rango,
.unwrap()sobreNone, dividir entre cero) provocan un panic automáticamente. - Usa
panic!para bugs y situaciones que deberían ser imposibles; usaResultpara fallos que quien llama puede manejar razonablemente. .expect("mensaje")es como.unwrap(), pero con un mensaje que hace el panic más fácil de depurar.assert!,assert_eq!yunreachable!hacen panic a propósito cuando algo que creías cierto resulta ser falso.
Comprueba lo que aprendiste
Sección titulada «Comprueba lo que aprendiste»Comprueba lo aprendido
1. ¿Qué hace una llamada a panic!?
2. Según la regla general de esta página, ¿cuándo deberías usar panic! en lugar de Result?
3. ¿Qué diferencia hay entre .unwrap() y .expect('mensaje')?
Puntuación: 0 / 3
Para leer más
Sección titulada «Para leer más»- Errores irrecuperables con panic! — El libro de Rust (en inglés): qué pasa durante un panic, qué es el unwinding (deshacer la pila) y cómo leer un backtrace.
- ¿Hacer panic! o no hacer panic!? — El libro de Rust (en inglés): las pautas oficiales para elegir entre
panic!yResult, con ejemplos. - panic! — Rust by Example (en inglés): un ejemplo corto de panic que muestra que la memoria se sigue liberando mientras el programa deshace la pila.
assert_eq!— documentación de std (en inglés): cómo funcionan las macros de aserción, incluidos los mensajes personalizados.panic!— documentación de std (en inglés): la referencia depanic!en sí, cómo se relaciona conunwrapy cuándo un panic es la herramienta adecuada.