Reference Counting
Bonjour à toutes et à tous 😀
Dans l'article sur le boxing, on a vu que le clonage était très loin d'être gratuit.
Aujourd'hui nous allons voir une manière de cloner presque gratuitement. 😉
Utilisons le même exemple.
Déclarons une structure Person
Elle contient deux champs:
- name : une String donc une référence et une slice quelque part en Heap
- age : un entier naturel
Déclarons une instance de Person
et affectons-la à la variable p
.
Nous nous retrouvons donc à avoir un espace mémoire dans la Stack qui est référencé par p
.
Mais ce n'est pas tout car p.name
référence aussi sa slice qui vie dans la Heap. (ici initialisé à "toto")
Nous possédons également p.age
qui réside dans la Stack.
Nous allons utiliser un autre outil de la bibliothèque standard qui se nomme Rc pour Reference Counting.
Tout comme pour Box
, la syntaxe est simple.
let rc = new;
12
est désormais dans la Heap, mais il va y avoir quelques différences avec le Box::new
. 😁
Appliquons le à p
.
Et voyons ce que ça produit en mémoire.
Tout d'abord on déplace p
dans le contexte de Rc::new
.
On vient wrapper nos données dans un conteneur qui possède deux champs:
- data : nos données
- count: un entier
Le compteur est initialisé à 1
.
En réalité le conteneur est une
RcBox
et le champcount
existe en deux version weak et strong. Mais pour les besoins de vulgarisations je ne rentrerai pas dans les détails.
On vient alors allouer avec un Box::new
de la place dans la Heap
On déplace alors vers la Heap notre RcBox
.
On récupère alors une référence vers la RcBox
Que l'on peut ensuite renvoyé dans le contexte main
.
On sort de la méthode Rc::new
ce qui a pour action de détruire la frame.
On se retrouve alors avec rc_p
une référence vers une RcBox
dans la Heap.
Bon pour le moment, ça semble juste un Box::new
avec des étapes supplémentaires, mais promis tout ça a un sens. 😇
Pour cela, clonons rc_p
.
Même combat que pour Box::clone
, on créé une référence que l'on copie dans Rc::clone
.
Mais cette fois-ci pas de vrai clone.
On incrémente seulement le compteur de la RcBox
.
Il passe de 1
à 2
.
On créé une nouvelle référence qui pointe vers la même RcBox
.
Et on retourne la référence dans le contexte main
.
Nous avons maintenant dans main
, deux références qui pointe vers la même RcBox
.
Le compteur de la RcBox
est de 2
.
Maintenant on décide de déplacer la Rc<Person>
, rc_p_clone
dans la méthode f1
.
Voyons ce qui se passe.
La référence est déplacée dans le contexte de f1
.
f1
termine.
Le drop est déclenché.
Il a pour effet de décrémenter de 1
le compteur de la RcBox
.
Mais comme le compteur est supérieur à 0
, il ne se passe rien.
f1
est terminé, mais la RcBox
est toujours dans la Heap
Appelons f1
avec rc_p
cette fois-ci, la dernière référence existante de la RcBox
.
Même combat, on déplace dans f1
.
f1
se termine
Le drop est déclenché, on décrémente le compteur.
Mais cette fois-ci, le compteur vaut maintenant 0.
On libère la mémoire occupée par la RcBox
et par la String
également.
f1
est terminée.
On détruit la frame
main
se termine
On détruit la frame, fin du programme.
Alors à quoi tout ça sert?
En synchrone, à presque rien, en tout cas je n'ai jamais eu d'exemple vraiment parlant qui n'aurait pas pu se régler par une référence non-mutable et une lifetime adéquate.
Par contre son grand-frère Arc
est la pierre angulaire de l'asynchronisme facile en Rust.
Il y a aussi le patterm de l'Interior Mutability qui utilise les Rc.
On verra ces deux aspects dans des prochains articles.
Tout ce que vous devez retenir c'est que cela permet à Rust de compter le nombre de référence active et de libérer la mémoire au moment propice.
C'est le même fonctionnement qu'un garbage collector, mais sans devoir arrêter le temps et l'espace pour compter vu que les objets se comptent eux-même.
Ce travail est sous licence CC BY-NC-SA 4.0.