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.
Este error apunta a un archivo que no escribimos, libcore / slice / mod.rs. Esa es la implementación de slice en el código fuente de Rust. El código que se ejecuta cuando usamos [] en nuestro vector v está en libcore / slice / mod.rs, ¡y ahí es donde entra el pánico!
La siguiente línea de nota nos dice que podemos configurar la variable de entorno RUST_BACKTRACE para obtener un seguimiento de lo que sucedió exactamente. Un backtrace es una lista de todas las funciones que se han llamado para llegar a este punto. Los backtraces en Rust funcionan como lo hacen en otros lenguajes: la clave para leer el backtrace es comenzar desde arriba y leer hasta que veas los archivos que escribiste. Ese es el lugar donde se originó el problema. Veamos un ejemplo :
$ RUST_BACKTRACE=1 cargo run
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
stack backtrace:
0: backtrace::backtrace::libunwind::trace
at /Users/runner/.cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.40/src/backtrace/libunwind.rs:88
1: backtrace::backtrace::trace_unsynchronized
at /Users/runner/.cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.40/src/backtrace/mod.rs:66
2: std::sys_common::backtrace::_print_fmt
at src/libstd/sys_common/backtrace.rs:84
3: <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmt
at src/libstd/sys_common/backtrace.rs:61
4: core::fmt::ArgumentV1::show_usize
5: std::io::Write::write_fmt
at src/libstd/io/mod.rs:1426
6: std::sys_common::backtrace::_print
at src/libstd/sys_common/backtrace.rs:65
7: std::sys_common::backtrace::print
at src/libstd/sys_common/backtrace.rs:50
8: std::panicking::default_hook::{{closure}}
at src/libstd/panicking.rs:193
9: std::panicking::default_hook
at src/libstd/panicking.rs:210
10: std::panicking::rust_panic_with_hook
at src/libstd/panicking.rs:471
11: rust_begin_unwind
at src/libstd/panicking.rs:375
12: core::panicking::panic_fmt
at src/libcore/panicking.rs:84
13: core::panicking::panic_bounds_check
at src/libcore/panicking.rs:62
14: <usize as core::slice::SliceIndex<[T]>>::index
at /rustc/5e1a799842ba6ed4a57e91f7ab9435947482f7d8/src/libcore/slice/mod.rs:2806
15: core::slice::<impl core::ops::index::Index<I> for [T]>::index
at /rustc/5e1a799842ba6ed4a57e91f7ab9435947482f7d8/src/libcore/slice/mod.rs:2657
16: <alloc::vec::Vec<T> as core::ops::index::Index<I>>::index
at /rustc/5e1a799842ba6ed4a57e91f7ab9435947482f7d8/src/liballoc/vec.rs:1871
17: panic::main
at src/main.rs:4
18: std::rt::lang_start::{{closure}}
at /rustc/5e1a799842ba6ed4a57e91f7ab9435947482f7d8/src/libstd/rt.rs:67
19: std::rt::lang_start_internal::{{closure}}
at src/libstd/rt.rs:52
20: std::panicking::try::do_call
at src/libstd/panicking.rs:292
21: __rust_maybe_catch_panic
at src/libpanic_unwind/lib.rs:78
22: std::panicking::try
at src/libstd/panicking.rs:270
23: std::panic::catch_unwind
at src/libstd/panic.rs:394
24: std::rt::lang_start_internal
at src/libstd/rt.rs:51
25: std::rt::lang_start
at /rustc/5e1a799842ba6ed4a57e91f7ab9435947482f7d8/src/libstd/rt.rs:67
26: panic::main
En Go, Recover es una función incorporada que recupera el control de una goroutine en pánico. Recuperar solo es útil dentro de funciones diferidas. Durante la ejecución normal, una llamada para recuperar devolverá cero y no tendrá ningún otro efecto. Si la goroutine actual entra en pánico, una llamada para recuperar capturará el valor dado al pánico y reanudará la ejecución normal.
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.