Skip to content

Commit 7c283f9

Browse files
committed
docs: improve explanations on stack and heap, and add details on dynamic dispatch
1 parent 17232d5 commit 7c283f9

File tree

2 files changed

+205
-96
lines changed

2 files changed

+205
-96
lines changed

src/es/new-concepts/generics-traits-and-static-dispatch.md

Lines changed: 1 addition & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Genericos
1+
# Genéricos
22

33
Tanto en Go como en Rust, los genéricos resuelven el mismo problema: evitar la
44
repetición de código cuando solo cambia el tipo de dato. Nos permiten escribir
@@ -326,92 +326,3 @@ A medida que avancemos en nuestro expertiz en Rust podremos notar que esto
326326
nos ayuda a generar APIs de librerias y experiencia de desarrollo realmente
327327
buena, simplificando muchas cosas.
328328

329-
330-
## Dynamic Dispatch
331-
332-
En algunas ocasiones, por ejemplo un caso muy típico es querer colecciones
333-
de tipos dinamicos, por ejemplo una lista de valores que quizás tienen poco en
334-
común, lo que podemos hacer es `dynamic dispatch`.
335-
336-
En Go tenemos el mismo impedimento por defecto, las listas solo pueden contener
337-
un tipo de dato a la vez, pero si se puede generar una interfaz en común para
338-
almacenar valores en la lista:
339-
340-
```go
341-
formas := []Dibujable{
342-
Circulo{Radio: 5},
343-
Rectangulo{Ancho: 3, Alto: 4},
344-
}
345-
346-
for _, f := range formas {
347-
fmt.Println(f.Dibujar())
348-
}
349-
```
350-
351-
De esta forma almacenamos tanto `Circulo` como `Rectangulo` en una única lista.
352-
353-
Cada elemento implementa la interfaz `Dibujable`. Internamente, Go usa punteros
354-
y tablas de métodos para hacer el dispatch.
355-
356-
En Rust, si quieres tener una lista de distintos tipos, necesitas usar trait
357-
objects de la siguiente forma:
358-
359-
```rust
360-
let formas: Vec<Box<dyn Dibujable>> = vec![
361-
Box::new(Circulo { radio: 5.0 }),
362-
Box::new(Rectangulo { ancho: 3.0, alto: 4.0 }),
363-
];
364-
365-
for forma in formas {
366-
println!("{}", forma.dibujar());
367-
}
368-
```
369-
370-
`Box<dyn Dibujable>` es un trait object. Esto implica `dynamic dispatch`, esto
371-
quiere decir que el método `dibujar` se resuelve en tiempo de ejecución. Rust
372-
validara en tiempo de compilación que se cumpla el contrato del `trait` pero
373-
no creara una función especifica para cada posible parámetro, sino que usara
374-
una única función.
375-
376-
Además el `dynamic dispatch` también responde a las reglas del ownership, por lo
377-
que si estamos diseñando una función podemos decidir si mover o hacer un
378-
prestamo del valor, si hacemos un prestamo/borrow no sera necesario utilizar
379-
`Box` para el parámetro de la función.
380-
381-
```rust
382-
trait Animal {
383-
fn hablar(&self);
384-
}
385-
386-
struct Perro;
387-
impl Animal for Perro {
388-
fn hablar(&self) {
389-
println!("Guau!");
390-
}
391-
}
392-
393-
fn con_prestamo(animal: &dyn Animal) {
394-
animal.hablar();
395-
}
396-
397-
fn con_movimiento(animal: Box<dyn Animal>) {
398-
animal.hablar();
399-
}
400-
401-
fn main() {
402-
let perro = Perro;
403-
404-
con_prestamo(&perro); // Prestamos el perro
405-
con_movimiento(Box::new(perro)); // Movemos el perro
406-
// Como usamos ownership perro no es accesible a partir de esta linea
407-
}
408-
```
409-
410-
Por lo que generalmente utilizar un prestamo/borrow nos simplifica el uso
411-
de `trait objects` en el caso de buscar `dynamic dispatch`.
412-
413-
En Go el dynamic dispatch es la única forma de relacionar parámetros con
414-
interfaces, internamente Go siempre hará dynamic dispatch.
415-
416-
A diferencia de Go, en Rust esto es opcional y explícito. Si no quieres dispatch
417-
dinámico, puedes usar `impl Trait` o `T: Trait`.

src/es/new-concepts/smart-pointers.md

Lines changed: 204 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,68 @@
22

33
En Go, los desarrolladores rara vez se preocupan de donde se almacenan los
44
datos en memoria. El lenguaje, junto al recolector de basura (Garbage Collector)
5-
se encarga automáticamente de decidir si una variable vive en el stack o en el
6-
heap, y se ocupa de liberarla cuando ya no es necesaria. Es un modelo cómodo.
5+
se encarga automáticamente de decidir si una variable vive en el `stack` o en el
6+
`heap`, y se ocupa de liberarla cuando ya no es necesaria. Es un modelo cómodo.
7+
En este capitulo explicaremos que es esto de `stack` y `heap`.
78

8-
En Rust, en cambio, no tenemos Garbage collector como hemos explicado en el
9-
capitulo [Ownership y Borrowing] y en [Mentalidad y Filosofía].
9+
En Rust, no tenemos Garbage collector como hemos explicado en el capitulo
10+
[Ownership y Borrowing] y en [Mentalidad y Filosofía].
1011
Es por eso que en su lugar Rust ofrece estos mecanismos, [Ownership y Borrowing]
1112
que garantizan seguridad sin pausas de recolección. Esto también significa
1213
que en ciertas ocasiones, el programador debe decidir explicitamente si un valor
1314
debe almacenarse en el heap y que hacer con él. En este punto es donde entran en
1415
juego los `smart pointers`.
1516

17+
## ¿Qué es `stack` y `heap`?
18+
19+
Exten dos grandes regiones de memoria donde se almacenan los datos:
20+
El `stack` (pila) y el `heap` (montículo).
21+
22+
Son dos caras de la misma moneda, son división lógicas que generalmente hace el
23+
sistema operativo con un objetivo en particular para cada uno:
24+
25+
- El `stack`: una memoria rápida, accesible y de tamaño fijo. Aquí se suelen
26+
almacenar los tipos de datos que tienen un tamaño conocido en tiempo de
27+
compilación y un tiempo de vida predecible.
28+
- El `heap`: una región de memoria flexible en la que podemos almacenar datos
29+
que pueden crecer dinámicamente o que tienen un tiempo de vida complejo o
30+
no muy bien definido, algo incierto.
31+
32+
En Go, por ejemplo cuando una variable es retornada desde una función o se
33+
utiliza por fuera del scope en el que fue inicialmente declarada, el compilador
34+
puede decidir automáticamente colocarla en el `heap`.
35+
Este proceso se conoce como `escape analysis`. Esto puede ocasionar problemas
36+
en algunas ocasiones cuando buscamos optimizar.
37+
38+
En Rust por otro lado podemos elegir de forma manual cuando queremos que un
39+
valor este disponible en el `heap` o en el `stack`.
40+
41+
Cuando almacenamos un valor en el `heap` independientemente del lenguaje de
42+
programación lo que sucederá es que el valor se crea en el `heap` y almacenamos
43+
la dirección de memoria del `heap` en el `stack`.
44+
45+
### ¿Por qué al almacenar en el `heap` también lo hacemos en el `stack`?
46+
47+
Esto es así porque el `stack` tiene un orden definido, entonces las
48+
búsquedas son rápidas, sin embargo el `heap` no tiene un orden por lo que todos
49+
los lenguajes asocian el identificador de la variable a una posición en el
50+
`stack` y a su vez esa posición en el `stack` almacenara la dirección de memoria
51+
para buscar en el `heap`, de esta forma nosotros podemos indexar los valores
52+
en el `heap` y que no sea tan costoso cada vez que debamos llamar una variable
53+
almacenada en el `heap`.
54+
55+
Entonces este es uno de los factores por los cuales el `stack` se considera más
56+
rápido, podemos acceder al valor de forma directa, pero en el `heap` debemos
57+
previamente ir al `stack` y luego con la dirección ir a buscar el valor en el
58+
`heap` es decir son dos lecturas que hacemos a diferencia del `stack` donde
59+
hacemos solo una.
60+
61+
No en todos los casos el `heap` es malo, si nosotros tenemos valores dinámicos,
62+
que cambiaran frecuentemente el `heap` es radicalmente un mejor lugar para
63+
almacenar las variables, eso debido a que el `stack` es menos flexible y
64+
suele ser más lento para escritura, el `heap`esta pensado para estas
65+
situaciones.
66+
1667
## ¿Qué es un Smart Pointer?
1768

1869
Un **smart pointer** en Rust es una estructura que se comporta como puntero,
@@ -33,11 +84,158 @@ Algunos smart pointers comunes en Rust son:
3384
Vamos a concentrarnos en `Box` en este capitulo, ya que es el más simple y el
3485
más útil para introducir el concepto de `heap allocation` y `dynamic dispatch`.
3586

87+
A medida que lo necesitemos iremos viendo otros smart pointers.
88+
3689
## ¿Qué es `Box<T>`?
3790

3891
`Box<T>` es el smart pointer más básico en Rust. Su único uso es mover un valor
39-
al heap.
92+
al `heap` y permitir acceder a él como si fuera una referencia exclusiva. `Box`
93+
almacena en el `stack`, pero el valor que contiene se almacena en el `heap`.
94+
Cuando la caja sale de alcance, el valor es automático liberado, gracias al
95+
[ownership] y al RAII.
96+
97+
```rust
98+
fn main() {
99+
let x = Box::new(42);
100+
println!("x = {}", x); // Se puede usar el valor de forma directa
101+
}
102+
```
103+
104+
Internamente `Box` implementa los traits `Deref` y `Drop`, dos traits muy
105+
importantes en los que profundizaremos en los proximos capitulos.
106+
107+
Lo que sucede en este código de ejemplo es que creamos un valor (`42`) y lo
108+
almacenamos en el `heap` cuando lo pasamos como parámetro a `Box`.
109+
`x` contendra un puntero al valor que ahora estará en el `heap`, por ergonomía
110+
Rust permite que si tenemos que acceder al valor de `x` accedamos de manera
111+
directa gracias a las reglas de [`dereference coercion`] (trait `Deref`) pero
112+
en realidad lo que estará sucediendo es que estarás interactuando con un
113+
puntero. Es decir, Rust da una capa de abstracción para no tener que interactuar
114+
con el puntero como tal, no obstante en capítulos mucho más avanzados veremos
115+
que podemos interactuar de forma directa con el puntero.
116+
117+
Ya hemos mencionado en la sección de este capitulo
118+
[¿Qué es `stack` y `heap`?](#qué-es-el-heap) algunos motivos por los cuales
119+
querremos usar `Box` y almacenar en el `heap`, pero de forma resumida podriamos
120+
decir que las situaciones son:
121+
- Cuando necesitas una estructura recursiva (Por ejemplo arboles o listas
122+
enlazadas)
123+
- Cuando el tamaño de datos es muy muy grande y no queremos copiarlo en cada
124+
lugar donde lo debemos utilizar
125+
- Cuando estás trabajando con tipos de tamaño desconocido en tiempo de
126+
compilación
127+
- Cuando estás trabajando con tipos desconocidos en tiempo de compilación
128+
- Cuando estas trabajando con APIs que requieren punteros o estructuras
129+
dinámicas
130+
131+
132+
A diferencia de punteros normales, Box es seguro, liberara automáticamente
133+
la memoria cuando el valor sale de scope, gracias al [ownership] y al trait
134+
`Drop`.
135+
136+
Nosotros en el capitulo anterior hablamos acerca de [Genéricos][Genericos]
137+
y vimos acerca del [`static dispatch`][static-dispatch], los beneficios que
138+
tiene pero también como contraparte vimos que si bien nos ahorra el escribir
139+
código también puede suceder que termine incrementando el tamaño del binario
140+
eso debido a que se creara una función especifica para cada tipo de dato que
141+
cumpla con el Genérico.
142+
143+
Con `Box` podemos solucionar este problema utilizando `dynamic dispatch`.
144+
145+
## Dynamic Dispatch
146+
147+
En algunas ocasiones, un caso muy típico es querer colecciones de tipos
148+
dinámicos, por ejemplo una lista de valores que quizás tienen poco en
149+
común, lo que podemos hacer para ese caso es `dynamic dispatch`.
150+
151+
En Go tenemos el mismo impedimento por defecto, las listas solo pueden contener
152+
un tipo de dato a la vez, pero si se puede generar una interfaz en común para
153+
almacenar valores en la lista:
154+
155+
```go
156+
formas := []Dibujable{
157+
Circulo{Radio: 5},
158+
Rectangulo{Ancho: 3, Alto: 4},
159+
}
160+
161+
for _, f := range formas {
162+
fmt.Println(f.Dibujar())
163+
}
164+
```
165+
166+
De esta forma almacenamos tanto `Circulo` como `Rectangulo` en una única lista.
167+
168+
Cada elemento implementa la interfaz `Dibujable`. Internamente, Go usa punteros
169+
y tablas de métodos para hacer el dispatch.
170+
171+
En Rust, si quieres tener una lista de distintos tipos, necesitas usar
172+
`trait objects` de la siguiente forma:
173+
174+
```rust
175+
let formas: Vec<Box<dyn Dibujable>> = vec![
176+
Box::new(Circulo { radio: 5.0 }),
177+
Box::new(Rectangulo { ancho: 3.0, alto: 4.0 }),
178+
];
179+
180+
for forma in formas {
181+
println!("{}", forma.dibujar());
182+
}
183+
```
184+
185+
`Box<dyn Dibujable>` es un trait object. Con esto hacemos `dynamic dispatch`,
186+
esto quiere decir que el método `dibujar` se resuelve en tiempo de ejecución.
187+
Rust validara en tiempo de compilación que se cumpla el contrato del `trait`
188+
pero no creara una función especifica para cada posible parámetro, sino que
189+
usara una única función.
190+
191+
Además el `dynamic dispatch` también responde a las reglas del ownership, por lo
192+
que si estamos diseñando una función podemos decidir si mover o hacer un
193+
prestamo del valor, si hacemos un prestamo/borrow no sera necesario utilizar
194+
`Box` para el parámetro de la función.
195+
196+
```rust
197+
trait Animal {
198+
fn hablar(&self);
199+
}
200+
201+
struct Perro;
202+
impl Animal for Perro {
203+
fn hablar(&self) {
204+
println!("Guau!");
205+
}
206+
}
207+
208+
fn con_prestamo(animal: &dyn Animal) {
209+
animal.hablar();
210+
}
211+
212+
fn con_movimiento(animal: Box<dyn Animal>) {
213+
animal.hablar();
214+
}
215+
216+
fn main() {
217+
let perro = Perro;
218+
219+
con_prestamo(&perro); // Prestamos el perro
220+
con_movimiento(Box::new(perro)); // Movemos el perro
221+
// Como usamos ownership perro no es accesible a partir de esta linea
222+
}
223+
```
224+
225+
Por lo que generalmente utilizar un prestamo/borrow nos simplifica el uso
226+
de `trait objects` en el caso de buscar `dynamic dispatch`.
227+
228+
En Go el dynamic dispatch es la única forma de relacionar parámetros con
229+
interfaces, internamente Go siempre hará dynamic dispatch.
230+
231+
A diferencia de Go, en Rust esto es opcional y explícito. Si no quieres dispatch
232+
dinámico, puedes usar `impl Trait` o `T: Trait`.
233+
40234

41235

42236
[Ownership y Borrowing]: ./../fundamental/ownership-and-borrowing.md
43-
[Mentalidad y Filosofía]: ./../mindset.md
237+
[Mentalidad y Filosofía]: ./../mindset.md
238+
[ownership]: ./../fundamental/ownership-and-borrowing.md#qué-es-el-ownership
239+
[`dereference coercion`]: https://book.rustlang-es.org/ch15-02-deref#coerciones-implicitas-de-deref-con-funciones-y-metodos
240+
[Genericos]: ./generics-traits-and-static-dispatch.md
241+
[static-dispatch]: ./generics-traits-and-static-dispatch.md#polimorfismo-con-static-dispatch

0 commit comments

Comments
 (0)