2
2
3
3
En Go, los desarrolladores rara vez se preocupan de donde se almacenan los
4
4
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 ` .
7
8
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] .
10
11
Es por eso que en su lugar Rust ofrece estos mecanismos, [ Ownership y Borrowing]
11
12
que garantizan seguridad sin pausas de recolección. Esto también significa
12
13
que en ciertas ocasiones, el programador debe decidir explicitamente si un valor
13
14
debe almacenarse en el heap y que hacer con él. En este punto es donde entran en
14
15
juego los ` smart pointers ` .
15
16
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
+
16
67
## ¿Qué es un Smart Pointer?
17
68
18
69
Un ** smart pointer** en Rust es una estructura que se comporta como puntero,
@@ -33,11 +84,158 @@ Algunos smart pointers comunes en Rust son:
33
84
Vamos a concentrarnos en ` Box ` en este capitulo, ya que es el más simple y el
34
85
más útil para introducir el concepto de ` heap allocation ` y ` dynamic dispatch ` .
35
86
87
+ A medida que lo necesitemos iremos viendo otros smart pointers.
88
+
36
89
## ¿Qué es ` Box<T> ` ?
37
90
38
91
` 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
+
40
234
41
235
42
236
[ 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