Situationen, in denen Cell oder RefCell die beste Wahl ist

8

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 ?

    
jocull 14.06.2015, 15:16
quelle

3 Antworten

14

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:

%Vor%

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:

gespeichert ist %Vor%

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".

    
Vladimir Matveev 14.06.2015, 16:46
quelle
7

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.

    
delnan 14.06.2015 16:36
quelle
6

Angenommen, Sie möchten ein Objekt des gewünschten Typs erstellen und in ein Rc ablegen.

%Vor%

Nun können Sie einfach ein weiteres Rc erstellen, das genau auf das gleiche Objekt und damit auf den Speicherort zeigt:

%Vor%

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 .

%Vor%

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.

    
oli_obk - ker 14.06.2015 16:44
quelle

Tags und Links