Vai al contenuto
eLearner.app
Modulo 6 · Lezione 2 di 212/14 nel corso~15 min
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:

  1. 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.
  2. Quando si vogliono trasferire dati di grandi dimensioni senza copiarli.

Esempio base:

Code
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):

Code
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 utilizzare Arc<T> (Atomic Reference Counted).

Code
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

Esercizio#rust.m6.l2.e1
Tentativi: 0Caricamento…

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 *.

Caricamento editor…
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)

Esercizio#rust.m6.l2.e2
Tentativi: 0Caricamento…

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.

Caricamento editor…
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

Esercizio#rust.m6.l2.e3
Tentativi: 0Caricamento…

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.

Caricamento editor…
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