È uscito il Corso SQL Completo

Smart Pointers in Rust

Gli smart pointers in Rust sono strutture dati che si comportano come puntatori ma offrono funzionalità aggiuntive come il conteggio dei riferimenti e la mutabilità interna.

Box<T> - Allocazione sullo Heap

Box<T> alloca un valore sullo heap, utile per tipi di dimensione sconosciuta a compile-time:

fn main() {
    let b = Box::new(5);
    println!("{}", b); // 5 - si usa come un riferimento normale

    // Utile per tipi ricorsivi
    enum Lista {
        Elemento(i32, Box<Lista>),
        Fine,
    }

    let lista = Lista::Elemento(1,
        Box::new(Lista::Elemento(2,
            Box::new(Lista::Fine))));
}

Rc<T> - Reference Counting

Rc<T> permette ownership condivisa tramite conteggio dei riferimenti (single-threaded):

use std::rc::Rc;

fn main() {
    let dati = Rc::new(vec![1, 2, 3]);

    let clone1 = Rc::clone(&dati); // Incrementa il contatore, non copia i dati
    let clone2 = Rc::clone(&dati);

    println!("Riferimenti: {}", Rc::strong_count(&dati)); // 3
    println!("{:?}", clone1); // [1, 2, 3]
}
// Tutti i Rc vengono deallocati quando il contatore arriva a 0

Arc<T> - Atomic Reference Counting

Arc<T> è come Rc<T> ma thread-safe:

use std::sync::Arc;
use std::thread;

fn main() {
    let dati = Arc::new(vec![1, 2, 3, 4, 5]);
    let mut handles = vec![];

    for i in 0..3 {
        let dati_clone = Arc::clone(&dati);
        let handle = thread::spawn(move || {
            println!("Thread {}: {:?}", i, dati_clone);
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }
}

RefCell<T> - Mutabilità Interna

RefCell<T> sposta il controllo del borrowing da compile-time a runtime:

use std::cell::RefCell;

fn main() {
    let dati = RefCell::new(vec![1, 2, 3]);

    // Borrow immutabile
    println!("{:?}", dati.borrow());

    // Borrow mutabile
    dati.borrow_mut().push(4);

    println!("{:?}", dati.borrow()); // [1, 2, 3, 4]

    // PANIC a runtime se si viola la regola dei borrow:
    // let r1 = dati.borrow();
    // let r2 = dati.borrow_mut(); // PANIC!
}

Pattern Comune: Rc + RefCell

Combinazione per ownership condivisa con mutabilità:

use std::cell::RefCell;
use std::rc::Rc;

#[derive(Debug)]
struct Nodo {
    valore: i32,
    figli: Vec<Rc<RefCell<Nodo>>>,
}

fn main() {
    let foglia = Rc::new(RefCell::new(Nodo {
        valore: 1,
        figli: vec![],
    }));

    let radice = Rc::new(RefCell::new(Nodo {
        valore: 0,
        figli: vec![Rc::clone(&foglia)],
    }));

    // Modifica la foglia tramite RefCell
    foglia.borrow_mut().valore = 10;
}

Weak<T> - Riferimenti Deboli

Weak<T> previene i cicli di riferimento con Rc:

use std::rc::{Rc, Weak};
use std::cell::RefCell;

struct Nodo {
    valore: i32,
    padre: RefCell<Weak<Nodo>>,
    figli: RefCell<Vec<Rc<Nodo>>>,
}

Deref e Drop

Gli smart pointers implementano Deref (per usarli come riferimenti) e Drop (per cleanup automatico):

use std::ops::Deref;

struct MioBox<T>(T);

impl<T> Deref for MioBox<T> {
    type Target = T;
    fn deref(&self) -> &T {
        &self.0
    }
}

Conclusione

Gli smart pointers estendono il sistema di ownership di Rust per gestire pattern complessi: Box per l’allocazione heap, Rc/Arc per l’ownership condivisa, RefCell per la mutabilità interna. Scegli Rc per codice single-threaded e Arc per codice multi-threaded. Usa Weak per prevenire cicli di riferimento.