Allocazione dinamica
In generale un puntatore è un oggetto che contiene un indirizzo di memoria, si dice che punta a una certa locazione dove possono essere memorizzati dei dati.
Rust ha il supporto a puntatori tramite le reference, che in qualche modo puntano alla locazione di memoria dive è memorizzato il valore.
Si chiamano invece smart pointers delle strutture dati che contengono un indirizzo di memoria, ma hanno anche altre caratteristiche e metadati. Questo concetto è originato dal C++, ed esistono anche in altri linguaggi.
Vediamo adesso il più semplice degli smart pointers, ovvero il tipo Box<T>.
Per default, un valore in Rust è memorizzato sullo stack, ma se vogliamo memorizzarlo sull'heap possiamo incapsularlo all'interno di un Box, in questo modo
fn main() { // x è un intero allocato sull'heap let x = Box::new(42); println!("Il valore è {}", x); }
Come ogni altro valore, possiamo accedere al contenuto semplicemente con il suo nome. Il risultato del programma è il seguente
Il valore è 42
Liste concatenate tramite Box
Da solo, il tipo Box non è molto utile, ma è indispensabile se per esempio vogliamo implementare strutture dati ricorsive.
Prendiamo per esempio una lista concatenata, possiamo provare a implementarla nel modo banale, ovvero come una enumerazione in cui in una variante abbiamo la lista vuota e nell'altra la concatenazione di un intero con un'altra lista.
#![allow(unused)] fn main() { enum List { Cons(i32, List), Nil, } }
Otteniamo però il seguente errore.
error[E0072]: recursive type `List` has infinite size
--> src/main.rs:1:1
|
1 | enum List {
| ^^^^^^^^^
2 | Cons(i32, List),
| ---- recursive without indirection
|
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to break the cycle
|
2 | Cons(i32, Box<List>),
| ++++ +
For more information about this error, try `rustc --explain E0072`.
Quando il compilatore va a inferire la dimensione del nostro oggetto, non riesce a farlo perché c'è una ricorsione infinita.
In particolare la dimensione di List è alcuni byte per memorizzare la variante e il valore intero, più la dimensione di List.
Possiamo risolvere il problema incapsulando la lista interna in un Box, come ci viene suggerito anche dal compilatore.
La struttura Box ha dimensione fissa (è un puntatore) e il calcolo della dimensione della struttura va a buon fine.
enum List { Cons(i32, Box<List>), Nil, } fn main() { // costruisco la lista 1 -> 2 -> Nil let list = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil)))); }
Ci sono molti altri contenitori oltre a Box<T>, che hanno altre feature, come per esempio il reference counting, accessi atomici, mutabilità anche come reference immutabili.
Per ulteriori dettagli si può consultare la documentazione ufficiale.