Box
Box
se trata de un tipo muy util en Rust. Permite almacenar en el heap
(el montón) un valor, en lugar de almacenarlo en la pila. Para crear un elemento de este tipo se usa Box::new()
con el elemento como parámetro.
fn just_takes_a_variable<T>(item: T) {} // Toma cualquier variable y la olvida. fn main() { let my_number = 1; // Este es de tipo i32 just_takes_a_variable(my_number); just_takes_a_variable(my_number); // no hay problema en usarla dos veces porque el tipo permite Copy let my_box = Box::new(1); // Este es de tipo Box<i32> just_takes_a_variable(my_box.clone()); // Sin .clone() la segunda función daría error just_takes_a_variable(my_box); // debido a que Box no dispone de Copy }
Al principio, resulta difícil pensar en la utilidad de este tipo, pero se usa mucho en Rust. Si se recuerda, &
se usa para los str
debido a que el compilador no conoce su tamaño: str
puede ser de cualquier longitud. Y la referencia &
sí que tiene un tamaño conocido, siempre igual. Box
se comporta de forma similar. Además, *
sirve para extraer el valor de un Box
. Igual que sucede con &
:
fn main() { let my_box = Box::new(1); // Este es Box<i32> let an_integer = *my_box; // Este es i32 println!("{:?}", my_box); println!("{:?}", an_integer); }
Por este motivo se denomina "puntero inteligente" a Box
, porque es como una referencia (un tipo de puntero), pero facilita otro conjunto de cosas.
Se puede usar Box
para crear un struct
con la misma estructura en su interior. A esto se le denomina estructura recursiva. Es decir, que dentro de un Struc A
puede haber otro Struct A
. En ocasiones, se puede necesitar Box
para crear listas enlazadas, aunque este tipo de listas no son muy populares en Rust. A continuación se muestra un ejemplo que muestra lo que sucede si se intenta crear una estructura recursiva sin Box
:
#![allow(unused)] fn main() { struct List { item: Option<List>, // ⚠️ } }
Este struct simple contiene un único elemento que puede ser Some<List>
o None
. Debido a que se puede incorporar este último, el struct no es recursivo hasta el infinito. Pero el compilador no puede conocer su tamaño para poder depositar sus elementos en la pila:
error[E0072]: recursive type `List` has infinite size
--> src/main.rs:3:1
|
3 | struct List {
| ^^^^^^^^^^^ recursive type has infinite size
4 | item: Option<List>, // ⚠️
| ------------ recursive without indirection
|
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `List` representable
Se observa que el propio compilador sugiere que se use Box
. Si se aplica este cambio:
struct List { item: Option<Box<List>>, } fn main() {}
Ahora el compilador acepta el struct List
, debido a que la recursividad se encuentra detrás de un Box
y el tamaño de este es conocido.
Un lista simple podría definirse así:
struct List { item: Option<Box<List>>, } impl List { fn new() -> List { List { item: Some(Box::new(List { item: None })), } } } fn main() { let mut my_list = List::new(); }
Incluso sin datos es un poco complejo. Rust no usa este tipo de patrón muy a menudo. Esto se debe a las estrictas reglas de propiedad y préstamo que tiene Rust. En todo caso, si se necesita un tipo cualquiera de lista enlazada, Box
puede ayudar.
Box
permite el uso de std::mem::drop
sobre un elemento de este tipo ya que el elemento se encuentra en el heap
(montón). Esto puede ser útil en ocasiones.