Box<T> è un semplice puntatore in Rust che crea e alloca un valore ad un indirizzo di memoria nell’heap. Lo usiamo quando lavoriamo con un tipo di dato di cui non conosciamo la dimensione in fase di compilazione, quando lavoriamo con molti dati a cui trasferire la proprietà senza copiarli e quando vogliamo lavorare con un tipo di dato che implementi determinati trait.
Lo usiamo anche quando lavoriamo con i dati ricorsivi un tipo di dati che contengono valori dello stesso tipo. In questo caso, è impossibile avere una lunghezza fissa perché ogni dato richiamerebbe un altro. Usare un puntatore risolve il problema perché ha una lunghezza fissa sullo stack mentre il valore viene conservato nell’heap. Un esempio di dati ricorsivi è quando creiamo una struttura ad albero come i nodi di un file HTML.
Il codice seguente verrà segnalato come errore:
enum Node {
Element(String,Node),
Nil
}
use crate::Node::Element;
fn main() {
let my_node = Element(
"html".to_string(),
Element(
"head".to_string(),
Node::Nil
)
);
}
Questo è il codice che usa Box<T>:
enum Node {
Element(String, Box<Node>),
Nil,
}
use crate::Node::Element;
fn main() {
let _my_node = Element(
"html".to_string(),
Box::new(Element("head".to_string(), Box::new(Node::Nil))),
);
}
Creazione dei puntatori personalizzati
Possiamo creare i nostri puntatori intelligenti e personalizzare i trait usati. Se vogliamo creare un puntatore simile a Box<T> dobbiamo usare un struct tuple.
struct MyBox<T>(T);
impl<T> MyBox<T> {
fn new(x: T) -> MyBox<T> {
MyBox(x)
}
}
Il trait Defer, ci permette di accedere al valore contenuto nel puntatore, altrimenti non possiamo farlo.
use std::ops::Deref;
struct MyBox<T>(T);
impl<T> MyBox<T> {
fn new(x: T) -> MyBox<T> {
MyBox(x)
}
}
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
fn hello(name: &str) {
println!("Hello, {name}!");
}
fn main() {
let marco = MyBox::new("Marco".to_string());
hello(&marco);
}
Con il trait Drop, possiamo dire a Rust cosa fare quando il valore viene deallocato. Ad esempio, mandare un messaggio che ci informa. Non è necessario richiamare il metodo perché avviene in automatico.
use std::ops::Deref;
struct MyBox<T>(T);
impl<T> MyBox<T> {
fn new(x: T) -> MyBox<T> {
MyBox(x)
}
}
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> Drop for MyBox<T> {
fn drop(&mut self) {
println!("Bye, bye")
}
}
fn hello(name: &str) {
println!("Hello, {name}!");
}
fn main() {
let marco = MyBox::new("Marco".to_string());
hello(&marco);
}