Wann müssten Sie Cell oder RefCell verwenden? Es scheint, als ob es viele andere Typenwahlen gäbe, die anstelle von diesen geeignet wären, und die Dokumentation warnt, dass die Verwendung von RefCell
ein bisschen ein "letzter Ausweg" ist.
Verwendet diese Typen einen " Code-Geruch "? Kann jemand ein Beispiel zeigen, in dem die Verwendung dieser Typen sinnvoller ist als die Verwendung eines anderen Typs, z. B. Rc
oder sogar Box
?
Es ist nicht ganz korrekt zu fragen, ob Cell
oder RefCell
über Box
und Rc
verwendet werden sollte, da diese Typen verschiedene Probleme lösen. Tatsächlich wird RefCell
häufig zusammen mit Rc
verwendet, um eine Veränderbarkeit mit gemeinsamem Besitz zu gewährleisten. Ja, die Anwendungsfälle für Cell
und RefCell
hängen vollständig von den Änderbarkeitsanforderungen in Ihrem Code ab.
Die Veränderbarkeit von Innen und Außen wird im offiziellen Rust-Buch, im designierten Kapitel zur Veränderbarkeit , sehr schön erklärt >. Die äußere Veränderlichkeit ist eng mit dem Eigentumsmodell verbunden, und wenn wir sagen, dass etwas veränderlich oder unveränderlich ist, meinen wir genau die externe Veränderlichkeit. Ein anderer Name für externe Veränderlichkeit ist ererbte Mutabilität, was wahrscheinlich das Konzept deutlicher erklärt: Diese Art von Änderbarkeit wird vom Eigentümer der Daten definiert und auf alles übertragen, was Sie vom Eigentümer erreichen können. Wenn zum Beispiel Ihre Variable eines Strukturtyps änderbar ist, gilt dies auch für alle Felder der Struktur in der Variablen:
%Vor%Vererbte Änderbarkeit definiert auch, welche Arten von Referenzen Sie aus dem Wert herausholen können:
%Vor% Manchmal ist jedoch die ererbte Veränderlichkeit nicht genug. Das kanonische Beispiel ist ein Referenzzähler, der in Rust Rc
genannt wird. Der folgende Code ist vollständig gültig:
Auf den ersten Blick ist nicht klar, wie die Veränderlichkeit damit zusammenhängt, aber erinnern Sie sich, dass reference-counted Zeigern so heißen, weil sie einen internen Referenzzähler enthalten, der modifiziert wird, wenn eine Referenz dupliziert wird ( clone()
in Rust) und zerstört (geht in Rust
aus dem Geltungsbereich). Daher muss Rc
sich selbst ändern, obwohl es in einer nicht mut
-Variablen gespeichert ist.
Dies wird durch interne Änderbarkeit erreicht. Es gibt spezielle Typen in der Standardbibliothek, von denen die grundlegendsten UnsafeCell
sind , die es erlauben, die Regeln der externen Veränderlichkeit zu umgehen und etwas zu mutieren, auch wenn es (transitiv) in einer nicht mut
-Variablen gespeichert wird.
Eine andere Möglichkeit zu sagen, dass etwas interne Veränderlichkeit hat, ist, dass dieses Etwas durch eine &
-Referenz geändert werden kann - das heißt, wenn Sie einen Wert vom Typ &T
haben und den Status von T
ändern können worauf es zeigt, dann hat T
interne Veränderlichkeit.
Zum Beispiel kann Cell
Copy
data enthalten und es kann mutiert werden, auch wenn es in nicht mut
location:
RefCell
kann nicht Copy
Daten enthalten und kann Ihnen &mut
Zeiger auf den enthaltenen Wert geben, und die Abwesenheit von Alias wird zur Laufzeit überprüft. Dies alles wird auf ihren Dokumentationsseiten ausführlich erklärt.
Wie sich herausstellte, können Sie in einer überwältigenden Anzahl von Situationen einfach nur mit externer Veränderbarkeit arbeiten. Der meiste existierende High-Level-Code in Rust ist so geschrieben. Manchmal ist jedoch interne Veränderlichkeit unvermeidlich oder macht den Code viel klarer. Ein Beispiel, Rc
Implementierung, wurde bereits oben beschrieben. Ein anderes ist, wenn Sie eine gemeinsame veränderbare Eigentümerschaft benötigen (dh Sie müssen auf denselben Wert aus verschiedenen Teilen Ihres Codes zugreifen und diesen ändern) - dies wird normalerweise über Rc<RefCell<T>>
erreicht, da dies nicht mit Referenzen alleine erfolgen kann. Ein weiteres Beispiel ist Arc<Mutex<T>>
, Mutex
ist ein anderer Typ für interne Veränderbarkeit, der auch sicher über Threads hinweg verwendet werden kann.
Wie Sie also sehen können, sind Cell
und RefCell
keine Ersetzungen für Rc
oder Box
; Sie lösen die Aufgabe, Ihnen irgendwo eine Veränderbarkeit zu bieten, wo dies standardmäßig nicht erlaubt ist. Sie können Ihren Code schreiben, ohne sie zu verwenden. und wenn Sie in eine Situation geraten, in der Sie sie brauchen würden, werden Sie es wissen.
Cell
s und RefCell
s sind kein Code-Geruch; Der einzige Grund, warum sie als "letzter Ausweg" bezeichnet werden, ist, dass sie die Aufgabe, Mutabilitäts- und Aliasregeln vom Compiler auf den Laufzeitcode zu überprüfen, wie im Fall RefCell
: Sie können nicht zwei &mut
s haben gleichzeitig auf die gleichen Daten zeigend, wird dies vom Compiler statisch erzwungen, aber mit RefCell
s können Sie dasselbe RefCell
anfordern, um Ihnen so viel &mut
s zu geben, wie Sie möchten - außer wenn Sie das tun Es wird mehr als einmal bei Ihnen Panik auslösen und zur Laufzeit Alias-Regeln erzwingen. Panics sind wohl schlimmer als Kompilierungsfehler, da Sie nur Fehler finden können, die sie zur Laufzeit und nicht zur Kompilierungszeit verursachen. Manchmal ist der statische Analysator im Compiler jedoch zu restriktiv, und Sie müssen ihn tatsächlich "umgehen".
Nein, Cell
und RefCell
sind keine "Code Smells". Normalerweise wird die Mutabilität vererbt , dh Sie können ein Feld oder einen Teil einer Datenstruktur genau dann ändern, wenn Sie exklusiven Zugriff auf die gesamte Datenstruktur haben und sich daher für die Änderbarkeit bei Diese Ebene mit mut
(dh foo.x
erbt ihre Veränderlichkeit oder das Fehlen davon von foo
). Dies ist ein sehr mächtiges Muster und sollte verwendet werden, wenn es gut funktioniert (was überraschend oft ist). Aber es ist nicht ausdrucksvoll genug für alle Codes überall.
Box
und Rc
haben damit nichts zu tun. Wie fast alle anderen Typen, respektieren sie die vererbte Veränderlichkeit: Sie können den Inhalt von Box
ändern, wenn Sie exklusiven, veränderbaren Zugriff auf Box
haben (weil das bedeutet, dass Sie auch exklusiven Zugriff auf den Inhalt haben). Umgekehrt können Sie niemals &mut
für den Inhalt von Rc
erhalten, da Rc
von Natur aus gemeinsam genutzt wird (d. H. Mehrere Rc
s können sich auf dieselben Daten beziehen).
Ein häufiger Fall von Cell
oder RefCell
ist, dass Sie veränderbare Daten zwischen mehreren Orten teilen müssen. Zwei &mut
Referenzen auf die gleichen Daten zu haben ist normalerweise nicht erlaubt (und das aus gutem Grund!). Manchmal brauchen Sie jedoch und die Zelltypen ermöglichen es, dies sicher zu tun.
Dies könnte über die übliche Kombination von Rc<RefCell<T>>
erfolgen, die es den Daten erlaubt, so lange zu bleiben, wie sie benutzt wird und es allen (aber nur jeweils einem!) erlaubt, sie zu mutieren. Oder es könnte so einfach wie &Cell<i32>
sein (selbst wenn die Zelle in einen aussagekräftigeren Typ gehüllt ist). Letzteres wird auch häufig für interne, private, veränderbare Status wie Referenzzählungen verwendet.
In der Dokumentation finden Sie einige Beispiele für die Verwendung von Cell
oder RefCell
. Ein gutes Beispiel ist tatsächlich Rc
selbst. Beim Erstellen eines neuen Rc
muss die Anzahl der Referenzen erhöht werden, aber die Anzahl der Referenzen wird zwischen allen Rc
s aufgeteilt. Daher kann dies durch vererbte Mutabilität nicht funktionieren. Rc
praktisch hat , um Cell
zu verwenden.
Eine gute Richtlinie ist es, so viel Code wie möglich ohne Zelltypen zu schreiben, aber sie zu verwenden, wenn es ohne sie zu sehr schmerzt. In einigen Fällen gibt es eine gute Lösung ohne Zellen, und mit Erfahrung werden Sie in der Lage sein, diese zu finden, wenn Sie sie vorher verpasst haben, aber es wird immer Dinge geben, die ohne sie nicht möglich sind.
Angenommen, Sie möchten ein Objekt des gewünschten Typs erstellen und in ein Rc
ablegen.
Nun können Sie einfach ein weiteres Rc
erstellen, das genau auf das gleiche Objekt und damit auf den Speicherort zeigt:
Da in Rust niemals eine veränderbare Referenz auf einen Speicherort vorhanden sein kann, auf den eine andere Referenz existiert, können diese Rc
-Container nie wieder geändert werden.
Was also, wenn Sie in der Lage sein möchten, diese Objekte zu ändern? und haben mehrere Rc
, die auf ein und dasselbe Objekt zeigen?
Dies ist das Problem, dass Cell
und RefCell
lösen. Die Lösung heißt "Interior Mutability" und bedeutet, dass die Alias-Regeln von Rust zur Laufzeit statt zur Kompilierungszeit erzwungen werden.
Zurück zu unserem ursprünglichen Beispiel:
%Vor% Um einen veränderbaren Verweis auf Ihren Typ zu erhalten, verwenden Sie borrow_mut
auf RefCell
.
Falls Sie den Wert, auf den Ihr Rc
s verweist, bereits ausgeliehen haben, wird die Funktion borrow_mut
in Panik geraten und daher die Alias-Regeln von Rust erzwingen.
Rc<RefCell<T>>
ist nur ein Beispiel für RefCell
, es gibt viele andere legitime Anwendungen. Aber die Dokumentation ist richtig. Wenn es einen anderen Weg gibt, benutze es, weil der Compiler dir nicht helfen kann, über RefCell
s nachzudenken.
Tags und Links rust