Eines der Hauptziele von Rust ist es, Änderungen an unveränderlichen Daten zu verhindern und Datenrennen zur Compile-Zeit zu vermeiden. Unter normalen Umständen erlaubt Rust es nicht, Daten über eine unveränderliche Referenz zu ändern. In Systemen mit Caching, lazy evaluation oder Logik, die eine Änderung des internen Zustands über eine Referenz erfordert, kann dies jedoch notwendig sein. Dafür wurde das Muster der interior mutability entwickelt.
Ohne interior mutability ist es kompliziert oder unmöglich, Caches, Lazy Initialisierung und viele andere Idiome zu implementieren, während man sicheres Eigentum und Referenzen bewahrt. Ein klassisches Beispiel ist ein Cache innerhalb einer Funktion, die der Außenwelt nur eine unveränderliche Referenz auf sich selbst bereitstellt.
In Rust gibt es spezielle Typen - Cell und RefCell (sowie deren Multithreading-Äquivalente), die es ermöglichen, den inneren Wert sogar über eine unveränderliche Referenz zu ändern, wobei die Sicherheit zur Laufzeit und nicht zur Compile-Zeit kontrolliert wird.
Cell<T> - ein Kopier-Primitiv, das das Ändern oder Lesen eines Wertes ohne die Verwendung von Referenzen erlaubt, jedoch nur für Typen, die Copy implementieren.RefCell<T> - ermöglicht es, eine veränderbare Referenz "on the fly" zu erhalten, selbst wenn nur eine unveränderliche externe Referenz vorhanden ist; wenn man jedoch versucht, zwei veränderbare oder eine veränderbare und mehrere unveränderliche Referenzen gleichzeitig zu erhalten, tritt eine Panik zur Laufzeit auf.Beispielcode:
use std::cell::RefCell; struct Foo { cache: RefCell<Option<u32>>, } impl Foo { fn get_or_compute(&self) -> u32 { if let Some(val) = *self.cache.borrow() { return val; } let computed = 42; *self.cache.borrow_mut() = Some(computed); computed } }
Wichtige Merkmale:
Können wir RefCell sicher in mehrteiligen Strukturen verwenden?
Nein, RefCell ist nicht threadsicher. Verwenden Sie Mutex oder RwLock für die Arbeit in einer mehrteiligen Umgebung.
Kann man eine Referenz auf den Inhalt von Cell<T> zurückgeben?
Nein, Cell gibt keine Referenzen zurück, sondern kopiert oder aktualisiert nur den Wert. Funktioniert nur mit Copy-Typen; für alle anderen verwenden Sie RefCell.
Was passiert, wenn man borrow_mut zweimal hintereinander für dasselbe RefCell aufruft?
Es tritt eine Panik zur Laufzeit auf, da RefCell die Anzahl der aktiven Referenzen verfolgt. Der zweite Versuch, veränderlichen Zugriff bei bereits vorhandener Referenz zu erhalten, führt zu einer Panik.
Im Projekt wird für die Speicherung von jeder veränderbaren Zustandsart innerhalb einer Struktur immer RefCell verwendet, selbst wenn veränderliches Eigentum genutzt werden könnte. Der Code wird wortreich, Paniken treten zur Laufzeit auf und das Testen wird schwierig.
Vorteile: Man kann schnell die Compiler-Beschränkungen umgehen und das gewünschte Verhalten erreichen
Nachteile: Hohes Risiko von Fehlern zur Laufzeit, Abstürze der Anwendung, schwierige Fehlerbehebung der Logik
RefCell wird nur zur Implementierung von faulen Caches in großen Strukturen verwendet, während im restlichen Code klassisches Eigentum und Referenzen beibehalten werden. Alle Werte werden korrekt bereinigt, es gibt keine Paniken.
Vorteile: Transparente Logik, Minimierung der Verwendung von interior mutability, der Code ist stabil und vorhersehbar
Nachteile: Erfordert Aufmerksamkeit in Bezug auf die Grenzen der Datenänderung: Das Lesen des Caches ist immer sicher, das Schreiben nur bei Bedarf und an genau definierten Stellen