Skip to content

Commit 7fe5465

Browse files
committed
feat: add error handling section and update code blocks for nullability examples
1 parent 957413b commit 7fe5465

File tree

3 files changed

+193
-3
lines changed

3 files changed

+193
-3
lines changed

src/es/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@
1515
- [Tuplas y Colecciones](./quick-comparisons/tuples-and-collections.md)
1616
- [Casting](./quick-comparisons/casting.md)
1717
- [Nulabilidad y Opcionalidad](./quick-comparisons/nullability-and-optionality.md)
18+
- [Manejo de errores](./quick-comparisons/error-handling.md)
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
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+

src/es/quick-comparisons/nullability-and-optionality.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ con respecto a este tema.
88
## Ejemplos rápidos
99

1010
En Go suele representarse la nubilidad de esta forma:
11-
```go
11+
```go,no_run
1212
some := 2 // short declaration
1313
var some int = 2
1414
var none *int = nil
@@ -17,7 +17,7 @@ var none *int = nil
1717
Hay varias formas de declarar la variable.
1818
Esto en Rust se representaria como:
1919

20-
```rust
20+
```rust,no_run
2121
let some = 1; // Short
2222
let some: i32 = 1; // declaración con tipo
2323
let some = Some(1); // Con inferencia
@@ -30,6 +30,10 @@ Por lo que una vez que logremos saber si el contenido es o no un valor valido,
3030
podemos usar diversos metodos para utilizar el contenido.
3131

3232
```go
33+
#package main
34+
#
35+
#import "fmt"
36+
#
3337
func main() {
3438
var username *string = nil
3539
if username == nil {
@@ -68,6 +72,10 @@ utilizando `nil`, lo usamos para representar un valor que es faltante, ausente
6872
o lógicamente no inicializado. Por ejemplo:
6973

7074
```go
75+
#package main
76+
#
77+
#import "fmt"
78+
#
7179
func search(id int) *string {
7280
if id == 1 {
7381
username := "Admin"
@@ -110,7 +118,7 @@ Rust sin embargo gracias a estas dos variantes, logra asegurar que estes
110118
teniendo en cuenta ambos casos, internamente este tipo de dato solamente se
111119
representa como un enum.
112120

113-
```rust
121+
```rust,no_run
114122
enum Option<T> {
115123
Some(T),
116124
None

0 commit comments

Comments
 (0)