22
33En Go, los desarrolladores rara vez se preocupan de donde se almacenan los
44datos 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] .
1011Es por eso que en su lugar Rust ofrece estos mecanismos, [ Ownership y Borrowing]
1112que garantizan seguridad sin pausas de recolección. Esto también significa
1213que en ciertas ocasiones, el programador debe decidir explicitamente si un valor
1314debe almacenarse en el heap y que hacer con él. En este punto es donde entran en
1415juego 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
1869Un ** smart pointer** en Rust es una estructura que se comporta como puntero,
@@ -33,11 +84,158 @@ Algunos smart pointers comunes en Rust son:
3384Vamos a concentrarnos en ` Box ` en este capitulo, ya que es el más simple y el
3485má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