Seguimos viendo el manejo de errores en Go y Rust.
En Go, Panic es una función incorporada que detiene el flujo ordinario de control y comienza a entrar en pánico. Cuando la función F llama a Panic, la ejecución de F se detiene, cualquier función diferida en F se ejecuta normalmente y luego F vuelve a su llamador. El proceso continúa en la pila hasta que todas las funciones de la goroutine actual han regresado, momento en el que el programa se bloquea. Los pánicos se pueden iniciar invocando el Panic directamente. También pueden deberse a errores en tiempo de ejecución.
Rust tambien tiene panic! pero en este caso en una macro, cuando panic! se ejecuta, el programa imprimirá un mensaje de error y limpiará la pila y luego se cerrará. Esto ocurre más comúnmente cuando se detecta un error de algún tipo y el programa no tiene claro cómo manejar el error.
Veamos un ejemplo para ver cómo es cuando se ejecuta panic!:
fn main() {
let v = vec![1, 2, 3];
v[99];
}
Aquí, estamos intentando acceder al elemento en la posición 100 de nuestro vector (que está en el índice 99 porque la indexación comienza en cero), pero solo tiene 3 elementos. En esta situación, Rust entrará en pánico. Se supone que el uso de [] devuelve un elemento, pero si pasa un índice no válido, no hay ningún elemento que Rust pueda devolver aquí que sea correcto.
En C, intentar leer más allá del final de una estructura de datos es un comportamiento indefinido. Puede obtener lo que esté en la ubicación de la memoria que corresponda a ese elemento en la estructura de datos, aunque la memoria no pertenezca a esa estructura. Esto se denomina sobrelectura del búfer y puede generar vulnerabilidades de seguridad si un atacante es capaz de manipular el índice de tal manera que lea datos que no deberían tener permitido y que se almacenan después de la estructura de datos.
Para proteger el programa de este tipo de vulnerabilidad, si intenta leer un elemento en un índice que no existe, Rust detendrá la ejecución y se negará a continuar. Probémoslo y veamos:
$ cargo run
Compiling panic v0.1.0 (file:///projects/panic)
Finished dev [unoptimized + debuginfo] target(s) in 0.27s
Running `target/debug/panic`
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', /rustc/5e1a799842ba6ed4a57e91f7ab9435947482f7d8/src/libcore/slice/mod.rs:2806:10
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
Aquí hay un programa de ejemplo que demuestra la mecánica del panic y el defer:
package main
import "fmt"
func main() {
f()
fmt.Println("Returned normally from f.")
}
func f() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered in f", r)
}
}()
fmt.Println("Calling g.")
g(0)
fmt.Println("Returned normally from g.")
}
func g(i int) {
if i > 3 {
fmt.Println("Panicking!")
panic(fmt.Sprintf("%v", i))
}
defer fmt.Println("Defer in g", i)
fmt.Println("Printing in g", i)
g(i + 1)
}
La función g toma el int i, y entra en pánico si i es mayor que 3, o se llama a sí misma con el argumento i + 1. La función f difiere una función que llama a recuperar e imprime el valor recuperado (si no es nulo). Esto da como resultado:
Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
Recovered in f 4
Returned normally from f.
Si eliminamos la función diferida de f, el pánico no se recupera y alcanza la parte superior de la pila de llamadas de goroutine, terminando el programa. Este programa modificado generará:
Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
panic: 4
panic PC=0x2a9cd8
[stack trace omitted]
La convención en las bibliotecas de Go es que incluso cuando un paquete usa panic internamente, su API externa aún presenta valores de retorno de error explícitos.