Lezioni del modulo (2/2)
Smart Pointers: Box ed Rc
Uno smart pointer (puntatore intelligente) è una struttura dati che si comporta come un puntatore, ma possiede metadati e funzionalità aggiuntive (come il conteggio dei riferimenti o la gestione automatica della memoria).
In Rust, i riferimenti ordinari (come &) prestano solo dati. Gli smart pointer, invece, spesso possiedono i dati a cui puntano. Esempi comuni includono String e Vec<T>, ma la libreria standard ne fornisce altri specializzati.
Box<T> per allocare nello Heap
Lo smart pointer più semplice è Box<T>, che memorizza i dati nello heap anziché nello stack. Sullo stack rimane solo il puntatore al dato allocato nello heap.
Si usa principalmente nei seguenti casi:
- Quando si ha un tipo la cui dimensione non può essere nota al momento della compilazione (come un tipo ricorsivo), e si vuole utilizzare un valore di quel tipo in un contesto che richiede una dimensione esatta.
- Quando si vogliono trasferire dati di grandi dimensioni senza copiarli.
Esempio base:
fn main() {
let b = Box::new(5); // alloca il valore 5 nello heap
println!("b = {}", *b); // de-referenzia con * per leggere il valore
}
Tipi Ricorsivi e Cons List
Un tipo ricorsivo è un tipo che può contenere un valore di se stesso al suo interno. Rust deve conoscere la dimensione esatta di un tipo a tempo di compilazione. Poiché la profondità di un tipo ricorsivo potrebbe essere infinita, il compilatore genera un errore a meno che non si utilizzi un Box (che ha dimensione fissa, in quanto è un puntatore):
enum List {
Cons(i32, Box<List>),
Nil,
}
Rc<T>: Conteggio dei Riferimenti (Reference Counting)
Ci sono casi in cui un singolo valore può avere più proprietari. Lo smart pointer Rc<T> (Reference Counted) tiene traccia del numero di riferimenti a un valore per determinare se il valore è ancora in uso. Se il conteggio scende a zero, la risorsa viene deallocata in sicurezza.
[!NOTE]
Rc<T>è utilizzabile esclusivamente in scenari single-thread. Per scenari multi-thread (concorrenti) si deve utilizzareArc<T>(Atomic Reference Counted).
use std::rc::Rc;
fn main() {
let a = Rc::new(5);
let b = Rc::clone(&a); // incrementa il contatore a 2
println!("Riferimenti: {}", Rc::strong_count(&a));
}
Prova tu
Esercizio 1: Allocare con Box
Alloca un valore intero pari a 42 nello heap utilizzando Box::new ed assegnalo ad una variabile chiamata val. Successivamente, stampa a schermo il valore memorizzato nella Box dereferenziandolo esplicitamente con l'operatore *.
Mostra suggerimento
Usa `let val = Box::new(42);` per memorizzare il valore in heap e dereferenzialo con `*val` all'interno di `println!`.
Soluzione disponibile dopo 3 tentativi
Esercizio 2: Struttura dati ricorsiva (Cons List)
Definisci un'enum ricorsiva chiamata List contenente due varianti: Cons che racchiude una tupla (i32, Box<List>), e Nil che rappresenta la fine della lista. Nel main, istanzia una lista concatenata contenente l'elemento 1 seguito da Nil.
Mostra suggerimento
L'enum si dichiara come `enum List { Cons(i32, Box<List>), Nil }`. Istanzia la lista con `List::Cons(1, Box::new(List::Nil))`.
Soluzione disponibile dopo 3 tentativi
Esercizio 3: Condividere la proprietà con Rc
Usa std::rc::Rc per creare un intero condiviso contenente il valore 100 all'interno di una variabile a. Clona il riferimento in una variabile b usando Rc::clone. Infine, stampa a schermo il contatore dei riferimenti attivi usando la funzione Rc::strong_count.
Mostra suggerimento
Crea il valore con `Rc::new(100)`. Clona con `Rc::clone(&a)`e visualizza il conteggio dei proprietari con`Rc::strong_count(&a)`.
Soluzione disponibile dopo 3 tentativi