Un des principaux objectifs de Rust est d'éviter la modification de données immuables et d'éviter les conditions de course à la compilation. Dans des conditions normales, Rust ne permettra pas de modifier des données à travers une référence immuable. Cependant, dans des systèmes avec mise en cache, calculs paresseux ou logique nécessitant des modifications de l'état interne via une référence, cela peut être nécessaire. C'est pourquoi le motif de mutabilité intérieure a été introduit.
Sans mutabilité intérieure, il est difficile ou impossible de mettre en œuvre des caches, l'initialisation paresseuse et de nombreuses autres idiomes tout en préservant la sécurité de la possession et des références. Un exemple classique est un cache à l'intérieur d'une fonction qui ne fournit au monde extérieur qu'une référence immuable sur elle-même.
En Rust, il existe des types spéciaux - Cell et RefCell (et leurs analogues pour la programmation multithread) - qui permettent de modifier une valeur interne même à travers une référence immuable, en contrôlant la sécurité à l'exécution plutôt qu'à la compilation.
Cell<T> - un primitive de copie, permet de modifier ou de lire une valeur sans utiliser de références, mais uniquement pour des types implémentant Copy.RefCell<T> - permet d'obtenir une référence mutable "à la volée", même en ayant seulement une référence externe immuable, mais si vous essayez d'obtenir simultanément deux références mutables ou une référence mutable et plusieurs références immuables, cela entraînera un panic à l'exécution.Exemple de code:
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 } }
Caractéristiques clés:
Pouvons-nous utiliser RefCell en toute sécurité pour des structures multithread?
Non, RefCell n'est pas sûr pour les threads. Utilisez Mutex ou RwLock pour travailler dans un environnement multithread.
Peut-on renvoyer une référence sur le contenu de Cell<T>?
Non, Cell ne fournit aucune référence, il ne fait que copier ou mettre à jour la valeur. Cela ne fonctionne qu'avec des types Copy, pour tous les autres utilisez RefCell.
Que se passe-t-il si l'on appelle borrow_mut deux fois d'affilée sur le même RefCell?
Un panic se produira à l'exécution, car RefCell suit le nombre de références actives. La deuxième tentative d'accès mutable alors qu'une référence existe déjà entraînera un panic.
Dans le projet, RefCell est toujours utilisé pour stocker n'importe quel état mutable à l'intérieur d'une structure, même si la possession mutable pourrait être utilisée. Le code devient verbeux, des panics surviennent à l'exécution, et il devient difficile de tester.
Avantages: On peut rapidement contourner les limitations du compilateur et obtenir le comportement désiré.
Inconvénients: Risque élevé d'erreurs à l'exécution, plantages d'application, débogage compliqué de la logique.
RefCell est utilisé uniquement pour mettre en œuvre des caches paresseux dans de grandes structures, et dans le reste du code, la possession classique et les références sont maintenues. Toutes les valeurs sont nettoyées correctement, pas de panics.
Avantages: Logique transparente, minimisation de l'utilisation de la mutabilité intérieure, code robuste et prévisible.
Inconvénients: Exige de faire attention aux limites de modification des données: la lecture du cache est toujours sûre, l'écriture uniquement si nécessaire et en des endroits étroitement définis.