|
| 1 | +# Manejo de errores |
| 2 | + |
| 3 | +El manejo de errores en Rust es nuevamente algo distinto al de Go, comparten |
| 4 | +algunas similitudes pero el scope de Rust es mucho más amplio. |
| 5 | + |
| 6 | +En esta sección veremos algunos de los contrastes pero no todos, más adelante |
| 7 | +veremos manejo de errores de forma avanzada. |
| 8 | + |
| 9 | +## Errores Irrecuperables |
| 10 | + |
| 11 | +Ambos lenguajes poseen algo llamado `panic` pero sin embargo se tratan de manera |
| 12 | +distinta. |
| 13 | + |
| 14 | +### 🐹 En Go, panic |
| 15 | + |
| 16 | +Detiene la ejecución del hilo actual (puede ser goroutine) y puede ser |
| 17 | +recuperado desde un `defer` usando `recover()`. |
| 18 | + |
| 19 | +```go |
| 20 | +#package main |
| 21 | +# |
| 22 | +#import "fmt" |
| 23 | +# |
| 24 | +func main() { |
| 25 | + defer func() { |
| 26 | + if r := recover(); r != nil { |
| 27 | + fmt.Println("¡Recuperado del panic!", r) |
| 28 | + } |
| 29 | + }() |
| 30 | + |
| 31 | + panic("¡Error fatal!") |
| 32 | +} |
| 33 | +``` |
| 34 | + |
| 35 | +Sin embargo este modelo de errores puede resultar relativamente costoso, |
| 36 | +No está optimizado para flujos normales de control, está pensado para |
| 37 | +situaciones excepcionales, no para control de errores. |
| 38 | + |
| 39 | +Cuando ocurre un panic, el runtime de Go captura una pila de llamadas completa |
| 40 | +(stack trace), ejecuta los `defer` en orden inverso. |
| 41 | + |
| 42 | +Además si hay un `recover`, detiene la propagación, si no, finaliza la goroutine. |
| 43 | + |
| 44 | +La propagación del estado interno del panic también puede ser relativamente |
| 45 | +costosa. |
| 46 | + |
| 47 | +Lanzar un `panic` y atraparlo con `recover` puede costar entre 10x y 100x más |
| 48 | +que retornar un error normalmente. |
| 49 | + |
| 50 | +En bucles su uso es bastante desaconsejado porque puede generar problemas |
| 51 | +serios. |
| 52 | + |
| 53 | +Hay algunos casos validos donde en Go se sugiere usar `panic`: |
| 54 | + |
| 55 | +- Librerías que deben abortar en casos imposibles. |
| 56 | +- En tests o mocks donde querés simular una falla brutal. |
| 57 | + |
| 58 | +### 🦀 RUST: `panic!` ligeros |
| 59 | + |
| 60 | +En Rust también tenemos una forma de usar un `panic` como dijimos antes pero |
| 61 | +suele ser más ligero en tema de costos principalmente por razones de diseño, |
| 62 | +control del programador, y modelo de ejecución. |
| 63 | + |
| 64 | +Profundizaremos más acerca de esto adelante pero para que se lleven una idea |
| 65 | +ahora: |
| 66 | +- No genera traceback por defecto para ser "recuperable", en parte porque no hay |
| 67 | + `defer` y no usas algo como `recover()` y es más lineal la ejecución, entonces |
| 68 | + Rust no necesita mantener estructuras para recuperar el control como Go pero |
| 69 | + sí imprime backtrace si se establece `RUST_BACKTRACE=1`, esta variable de |
| 70 | + entorno hace que se calcule sólo cuando se va a imprimir, no al hacer `panic!` |
| 71 | +- Rust sigue RAII mientras que Go usa un poor man's RAII, lo que simplifica el |
| 72 | + como destruir las instancias, y con el trait `Drop` (el cual se implementa por |
| 73 | + defecto en todo) garantizan limpieza sin `defer` |
| 74 | +- Rust tiene configuraciones en tiempo de compilación para entender cual sera el |
| 75 | + comportamiento por defecto, si sera `panic = "abort"` o `panic = "unwind"`, |
| 76 | + con el `unwind` es más sutil y libera la memoria progresivamente, mientras |
| 77 | + que el `abort` puede ser más violento, directamente termina el programa, cero |
| 78 | + overhead. |
| 79 | + |
| 80 | +En Rust, la macro `panic!` no es parte del flujo normal del programa, así |
| 81 | +que el compilador puede optimizar mejor todo el código alrededor. |
| 82 | + |
| 83 | +Por último si, generalmente se considera al `panic!` como un tipo de error |
| 84 | +irrecuperable en Rust, sin embargo tenemos una forma de hacerlo si queremos, |
| 85 | +aunque no es la forma más idiomática, que es mediante la función `catch_unwind` |
| 86 | +la cual nos permitirá manejar un `panic!` como un tipo de dato que veremos más |
| 87 | +adelante, el `Result`, y con ello podremos administrar el error como si fuese |
| 88 | +otro error típico de nuestra código, bastante útil para algunos casos |
| 89 | +específicos. |
| 90 | + |
| 91 | +Veamos esta comparativa para que quede más claro: |
| 92 | + |
| 93 | +| Característica | Rust `panic!` | Go `panic` + `recover` | |
| 94 | +| ------------------------ | --------------------------------- | -------------------------------- | |
| 95 | +| ¿Backtrace automático? | ❌ Solo si `RUST_BACKTRACE=1` | ✅ Siempre capturado internamente | |
| 96 | +| ¿Recuperación integrada? | ❌ No por defecto (`catch_unwind`) | ✅ Con `recover()` desde `defer` | |
| 97 | +| ¿Limpieza de recursos? | ✅ Con `Drop` (RAII) | ✅ Con `defer` | |
| 98 | +| ¿Costo relativo? | ⚙️ Bajo (abort) / Medio (unwind) | 🚨 Alto (defer + recover + pila) | |
| 99 | +| ¿Se puede desactivar? | ✅ Sí (`panic = "abort"`) | ❌ No | |
| 100 | + |
| 101 | +El caso más simple si queremos que algo falle es: |
| 102 | + |
| 103 | +```rust |
| 104 | +fn main() { |
| 105 | + panic!("¡Esto explotó!"); |
| 106 | +} |
| 107 | +``` |
| 108 | + |
| 109 | +Esto al ejecutarlo nos dara el error `¡Esto explotó!` y nos dice que si |
| 110 | +agregamos el `RUST_BACKTRACE=1` obtendremos más información. |
| 111 | + |
| 112 | +Solo para ejemplificar un poco más en el siguiente ejemplo si se desea ejecutar |
| 113 | +mostrara el backtrace de forma resumida porque para este ejemplo hemos |
| 114 | +establecido la variable de entorno `RUST_BACKTRACE` en `1`: |
| 115 | + |
| 116 | +```rust |
| 117 | +#use std::env; |
| 118 | +# |
| 119 | +fn main() { |
| 120 | +# env::set_var("RUST_BACKTRACE", "1"); |
| 121 | + funcion_peligrosa(); |
| 122 | + println!("¡Este mensaje no sera mostrado porque falla antes!") |
| 123 | +} |
| 124 | + |
| 125 | +fn funcion_peligrosa() { |
| 126 | + panic!("¡Esto explotó!"); |
| 127 | +} |
| 128 | +``` |
| 129 | + |
| 130 | +#### Diseccionando el backtrace |
| 131 | + |
| 132 | +Vamos a revisar un poco el mensaje que nos da la ejecución del ejemplo anterior |
| 133 | +solo para darte algunas herramientas en caso de ver este mensaje en alguna |
| 134 | +ocasión: |
| 135 | + |
| 136 | +```sh |
| 137 | +¡Esto explotó! # 👈 Nuestro mensaje de error |
| 138 | +stack backtrace: |
| 139 | + 0: std::panicking::begin_panic # 👈 Aquí comienza el panico |
| 140 | + at /rustc/6b00bc3880198600130e1cf62b8f8a93494488cc/library/std/src/panicking.rs:769:5 |
| 141 | + 1: playground::funcion_peligrosa # 👈 Ocurrio dentro de funcion_peligrosa |
| 142 | + at ./src/main.rs:9:5 |
| 143 | + 2: playground::main # 👈 funcion_peligrosa fue llamada en el main |
| 144 | + at ./src/main.rs:5:5 # 👈 y especificamente en la linea 5 |
| 145 | + # nosotros ocultamos código solo para que puedas |
| 146 | + # ver el error pero si, se llama en la linea 5 |
| 147 | + 3: core::ops::function::FnOnce::call_once |
| 148 | + at ./.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5 |
| 149 | +note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace. |
| 150 | +``` |
| 151 | + |
| 152 | +Por ultimo nos dice que podemos usar `RUST_BACKTRACE=full` para mostrar el |
| 153 | +backtrace completo, el cual seria mucho más largo e indicaria más detalles pero |
| 154 | +al mismo tiempo seria mucho más complicado de leer para alguien que comienza. |
| 155 | + |
| 156 | +#### Recuperando un `panic!` |
| 157 | + |
| 158 | +```rust |
| 159 | +#use std::panic; |
| 160 | +# |
| 161 | +fn main() { |
| 162 | + let resultado = panic::catch_unwind(|| { |
| 163 | + println!("¡Va a ejecutar algo que falla!"); |
| 164 | + |
| 165 | + panic!("¡Ups fallo algo!"); |
| 166 | + }); |
| 167 | + |
| 168 | + match resultado { |
| 169 | + Ok(_) => println!("No hubo pánico."), |
| 170 | + Err(_) => println!("¡Se capturó un panic!"), // Podríamos recuperar el mensaje |
| 171 | + } |
| 172 | + |
| 173 | + println!("¡Pero la ejecución continuo!") |
| 174 | +} |
| 175 | +``` |
| 176 | + |
| 177 | +De esta forma estamos manejando el posible panico. |
| 178 | + |
| 179 | +--- |
| 180 | + |
| 181 | + |
0 commit comments