ProgrammazioneSviluppatore Backend

Come funzionano i puntatori intelligenti in Rust (Box, Rc, Arc, RefCell)? Qual è la differenza tra di loro e in quali casi è meglio scegliere ciascuno?

Supera i colloqui con l'assistente IA Hintsage

Risposta

In Rust non esiste un garbage collector classico, quindi per gestire la proprietà di strutture complesse si utilizzano puntatori intelligenti (smart pointers). I più comunemente usati sono:

  • Box<T> — alloca memoria per un oggetto nel heap e trasferisce la sua proprietà. È usato nei casi in cui la dimensione dei dati non è nota durante la compilazione o è necessario un risorsa spostabile ma unica.

  • Rc<T> (Reference Counted) — conteggio dei riferimenti, consente a più variabili di "condividere" la proprietà di dati immutabili (solo in un contesto monothread).

  • Arc<T> (Atomic Reference Counted) — implementa anche il conteggio dei riferimenti, ma è atomico; è utilizzato nei programmi multithread.

  • RefCell<T> — fornisce proprietà "internamente mutabile" a runtime, consentendo di cambiare il contenuto anche attraverso un riferimento immutabile, ma solo in un singolo thread (non è thread-safe!).

Esempio:

use std::rc::Rc; let a = Rc::new(vec![1,2,3]); let b = Rc::clone(&a); // Ora sia a che b sono proprietari degli stessi dati

Domanda trabocchetto

È possibile utilizzare Rc<T> nel codice multithread, se tutti i thread leggono solo i dati? Spiega.

Risposta: No, non è possibile! Anche se Rc<T> consente solo l'accesso immutabile ai dati, il contenitore stesso Rc<T> non è thread-safe, poiché il numero interno di riferimenti non è protetto dalle condizioni di gara. A questo scopo è destinato Arc<T>, il cui contatore interno è thread-safe.

Esempio:

// Il seguente codice non compila! use std::thread; use std::rc::Rc; let five = Rc::new(5); for _ in 0..10 { let five = Rc::clone(&five); thread::spawn(move || { println!("{}", five); }); }

Esempi di errori reali a causa della mancanza di conoscenza delle sottigliezze dell'argomento


Storia

Hanno cercato di usare Rc<T> per condividere una cache tra i thread al fine di accelerare un servizio web. In produzione hanno ottenuto crash strani e dati corrotti. Dopo un'indagine, si è scoperto che Rc non è thread-safe, e il contatore di riferimenti era danneggiato. Soluzione: sostituzione con Arc<T>.

Storia

In un'applicazione desktop, un grande albero di oggetti era conservato in Box<T>, ma non si era considerato che più parti dell'interfaccia utente dovevano condividere la proprietà dei dati. Questo ha portato a errori di compilazione. La soluzione è stata utilizzare Rc<T> per condividere l'accesso.

Storia

Nel modulo di logica aziendale hanno utilizzato RefCell<T> per organizzare l'accesso mutabile ai dati, che venivano anche trasferiti tramite Arc<T> tra i thread. Ma cercare di combinare RefCell<T> e Arc<T> ha portato a condizioni di gara e panico durante l'esecuzione. Per una versione thread-safe sarebbe stato meglio usare Mutex<T> o RwLock<T> al posto di RefCell<T>.