ProgrammierungBackend Entwickler

Wie funktionieren Smart Pointers in Rust (Box, Rc, Arc, RefCell)? Was sind die Unterschiede zwischen ihnen und in welchen Fällen sollte man welchen wählen?

Bestehen Sie Vorstellungsgespräche mit dem Hintsage-KI-Assistenten

Antwort

In Rust gibt es keinen klassischen Garbage Collector, daher werden Smart Pointers zur Verwaltung des Besitzes komplexer Strukturen verwendet. Die am häufigsten verwendeten sind:

  • Box<T> — allokiert Speicher für ein Objekt auf dem Heap und überträgt den Besitz davon. Wird verwendet, wenn die Größe der Daten zur Kompilierzeit nicht bekannt ist oder ein beweglicher, aber einzigartiger Resource benötigt wird.

  • Rc<T> (Reference Counted) — Referenzzählung, ermöglicht es mehreren Variablen, den Besitz unveränderlicher Daten zu „teilen“ (nur im einsträngigen Kontext).

  • Arc<T> (Atomic Reference Counted) — implementiert ebenfalls Referenzzählung, jedoch atomar; die Verwendung ist in mehrsträngigen Programmen zulässig.

  • RefCell<T> — bietet „interne veränderliche“ Besitze zur Laufzeit, was es ermöglicht, den Inhalt selbst über eine unveränderliche Referenz zu ändern, jedoch nur in einem Thread (nicht threadsicher!).

Beispiel:

use std::rc::Rc; let a = Rc::new(vec![1,2,3]); let b = Rc::clone(&a); // Jetzt sind sowohl a als auch b - Besitzer der gleichen Daten

Trickfrage

Kann man Rc<T> in mehrsträngigem Code verwenden, wenn alle Threads nur Daten lesen? Erklären Sie.

Antwort: Nein, das kann man nicht! Obwohl Rc<T> nur unveränderlichen Zugriff auf die Daten ermöglicht, ist der Container Rc<T> selbst nicht threadsicher, da die interne Anzahl der Referenzen nicht vor Datenrennen geschützt ist. Dafür ist Arc<T> vorgesehen — sein interner Zähler ist threadsicher.

Beispiel:

// Der folgende Code wird nicht kompilieren! 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); }); }

Beispiele für reale Fehler aufgrund von Unkenntnis der Feinheiten des Themas


Geschichte

Es wurde versucht, Rc<T> zu verwenden, um den Cache zwischen Threads zu teilen, um einen Web-Service zu beschleunigen. Im Betrieb gab es seltsame Abstürze und beschädigte Daten. Nach einer Untersuchung stellte sich heraus, dass Rc nicht threadsicher ist und der Referenzzähler beschädigt wurde. Lösung: Ersatz durch Arc<T>.

Geschichte

In einer Desktop-Anwendung wurde ein großes Objektbaum in Box<T> gespeichert, aber es wurde nicht berücksichtigt, dass mehrere Teile der Benutzeroberfläche den Besitz der Daten teilen sollten. Dies führte zu Kompilierungsfehlern. Die Lösung war die Verwendung von Rc<T> zum Teilen des Zugriffs.

Geschichte

In einem Geschäftslogik-Modul wurde RefCell<T> verwendet, um einen veränderbaren Zugriff auf Daten zu organisieren, die auch über Arc<T> zwischen Threads weitergegeben wurden. Aber der Versuch, RefCell<T> und Arc<T> zu kombinieren, führte zu Datenrennen und Panik zur Laufzeit. Für die threadsichere Variante sollte Mutex<T> oder RwLock<T> anstelle von RefCell<T> verwendet werden.