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.