Een van de belangrijkste doelstellingen van Rust is om wijzigingen aan onveranderlijke gegevens te voorkomen en dataraces tijdens de compilatie te vermijden. Onder normale omstandigheden staat Rust niet toe om gegevens te wijzigen via een ongewijzigde referentie. Echter, in systemen met caching, lazy evaluation of logica die het vereist om de interne staat via een referentie te wijzigen, kan dit nodig zijn. Daarom werd het patroon interior mutability geïntroduceerd.
Zonder interior mutability is het moeilijk of onmogelijk om caches, lazy initialisatie en veel andere idiomen te implementeren terwijl een veilige eigendom en referenties behouden blijven. Een klassiek voorbeeld is een cache binnen een functie die de externe wereld alleen een ongewijzigde referentie op zich aanbiedt.
In Rust zijn er speciale types — Cell en RefCell (en hun analogen voor multithreading) — die het mogelijk maken om de interne waarde zelfs via een ongewijzigde referentie te wijzigen, waarbij de veiligheid tijdens de runtime wordt gecontroleerd in plaats van tijdens de compilatie.
Cell<T> — een copy-primitief dat het mogelijk maakt om een waarde te wijzigen of te lezen zonder referenties, maar alleen voor types die Copy implementeren.RefCell<T> — maakt het mogelijk om "on-the-fly" een wijzigbare referentie te verkrijgen, zelfs met alleen een ongewijzigde externe referentie, maar als je probeert om tegelijkertijd twee wijzigbare referenties of één wijzigbare en meerdere ongewijzigde referenties te verkrijgen — zal dit tot een paniek in de uitvoering leiden.Voorbeeldcode:
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 } }
Belangrijke kenmerken:
Kunnen we RefCell veilig gebruiken voor multi-threaded structuren?
Nee, RefCell is niet thread-safe. Gebruik Mutex of RwLock voor gebruik in een multi-threaded omgeving.
Kan je een referentie teruggeven naar de inhoud van Cell<T>?
Nee, Cell geeft geen referenties terug, het kopieert of werkt alleen de waarde bij. Werkt alleen met Copy-types, voor alle andere type gebruik RefCell.
Wat gebeurt er als je borrow_mut twee keer achter elkaar oproept op dezelfde RefCell?
Er zal een panic optreden tijdens runtime, omdat RefCell het aantal actieve referenties bijhoudt. De tweede poging om wijzigbare toegang te krijgen bij een reeds bestaande referentie zal leiden tot paniek.
In het project wordt altijd RefCell gebruikt voor het opslaan van elke wijzigbare status binnen een structuur, zelfs als wijzigbaar eigendom kan worden gebruikt. De code wordt omslachtig, er ontstaan panieken tijdens runtime, en het wordt moeilijk om te testen.
Voordelen: Je kunt snel de beperkingen van de compiler omzeilen en het gewenste gedrag bereiken.
Nadelen: Hoge kans op fouten tijdens uitvoering, crashes van de applicatie, moeilijke debug logica.
RefCell wordt alleen gebruikt voor de implementatie van lazy caches in grote structuren, en in de rest van de code blijven de klassieke eigendom en referenties bestaan. Alle waarden worden correct gewist, er zijn geen panieken.
Voordelen: Transparante logica, minimalisatie van het gebruik van interior mutability, de code is robuust en voorspelbaar.
Nadelen: Vereist aandacht voor de grenzen van gegevenswijzigingen: het lezen van de cache is altijd veilig, schrijven alleen indien nodig en op nauwkeurig gedefinieerde plaatsen.