Éviter l'abus des esperluettes en Rust
À la lecture du livre Le langage de programmation Rust, bien qu’il soit bien expliqué, j’ai rencontré des difficultés à me familiariser avec les notions de possession et d’emprunt, propres au langage Rust. Néanmoins, je suis tombé sur un article très éclairant sur ces notions à première vue compliquées.
Le post qui suit est une traduction en français de l’article Getting Past “Ampersand-Driven Development” in Rust, écrit par Evan Schwartz.
Introduction
Un petit modèle mental pour les notions de possession et d’emprunt.
J’ai entendu l’expression “ampersand-driven development” (développement axé sur les esperluettes) dans une conférence de Tad Lispy. Celle-ci a immédiatement illustré l’expérience d’un nouveau développeur Rust insérant aléatoirement des esperluettes pour satisfaire le compilateur Rust.
Ce billet décrit un petit modèle mental que j’ai utilisé pour expliquer à quelqu’un qui découvre Rust la différence entre &
, &mut
, les valeurs possédées, les Rc
et les Arc
. J’espère que vous ou d’autres aspirants Rustacéens le trouverez utile !
Références (&variable
)
Commençons par les esperluettes. La redoutable esperluette. Vous la voyez partout en Rust - et si vous essayez d’écrire du code Rust pour la première fois, il ne se passera probablement pas plus de 10 minutes avant que le compilateur Rust ne vous dise, de manière agaçante ou utile, que vous devez mettre une esperluette quelque part.
Imaginons une fonction simple qui calcule la longueur d’une chaîne de caractères. Cette fonction doit lire la chaîne. Mais est-elle autorisée à la modifier ? Non. Lorsque l’exécution de la fonction est terminée, doit-on supprimer la chaîne de la mémoire ? Non. Cela signifie que la fonction longueur n’a besoin que d’un accès en lecture seule, et qu’elle n’a besoin que d’une vue temporaire de la chaîne plutôt que d’une version permanente.
C’est ce que signifie la notation &variable
en Rust. Pensez à un petit enfant qui prête son jouet préféré à un autre enfant en lui disant : “Tu peux regarder, mais tu ne peux pas toucher. Quand tu auras fini, je veux que tu me le rendes”. Il s’agit d’une référence partagée.
Références mutables (&mut variable
)
Quid de &mut variable
?
À la place de notre fonction longueur, imaginons une fonction qui ajoute le préfixe “hello” à une chaîne de caractères donnée. Dans ce cas, on veut que la fonction modifie la valeur qui lui est donnée. Nous avons donc besoin de &mut
ou d’une référence mutable.
Pensez à notre petit enfant qui prête un livre de coloriage à un ami pour qu’il colorie une page : “Tu peux regarder et toucher, mais quand tu as fini, je veux quand même que tu me le rendes.”.
Les références mutables sont exclusives
Voici un bon endroit pour expliquer l’une des parties subtiles mais très intelligentes de Rust.
Si quelqu’un (en réalité, un bout de votre code) a une référence mutable vers une valeur, le compilateur Rust s’assure que personne d’autre ne peut avoir de référence vers cette valeur. Pourquoi ? Pour la raison que, si vous regardez une valeur que vous pensez être immuable, il n’y ait personne qui la change inopinément alors que vous êtes en train de l’utiliser (imaginez la confusion que ce serait pour la fonction longueur que nous avons mentionnée plus haut !).
Cela implique que si quelqu’un possède, ne serait-ce qu’une référence immuable vers une valeur, nous ne pouvons pas la modifier ni en récupérer une référence mutable.
Valeurs possédées (variable
)
Ensuite, nous allons parler des valeurs possédées.
Un autre aspect astucieux de Rust est la façon dont il détermine quand les valeurs doivent être oubliées ou supprimées de la mémoire. Lorsque l’exécution d’une fonction est terminée, toutes les valeurs déclarées en son sein sont supprimées ou automatiquement nettoyées.
Ce n’est pas tout à fait vrai. Si nous repensons à la fonction longueur de chaîne de caractères, nous ne voulons pas que la chaîne soit complètement oubliée lorsque l’exécution de la fonction est terminée. Il en va de même pour la fonction qui ajoute le préfixe “hello”. Dans ces cas-ci, seules les références à la valeur seront nettoyées, mais la valeur réelle ne sera pas oubliée.
Qu’en est-il lorsqu’on insère quelque chose dans un HashMap
? Dans ce cas, nous voulons que la chaîne de caractères que nous avons saisie devienne une partie du HashMap
. Nous voulons que la valeur soit possédée par le HashMap
.
Pensez à un petit enfant qui donne l’un de ses jouets : “Voilà, tu peux l’avoir. Tu peux en faire ce que tu veux, et je n’ai pas besoin qu’on me le rende. Amuse-toi bien !” (L’enfant imaginé devrait être assez mature pour que cette scène soit crédible).
Pointeurs intelligents compteurs de références (Rc
et Arc
)
Deux autres types de valeurs disponibles en Rust sont : Rc
et Arc
.
Pour un Rc
, imaginez des décorations telles que des ballons lors d’une fête d’anniversaire. Pendant que tout le monde est là, on veut que tout le monde voie les décorations sans les toucher. Et on veut que les décorations restent en place jusqu’à ce que le dernier enfant quitte la fête. Mais dès que le dernier est parti, on peut immédiatement commencer à nettoyer les décorations. Il s’agit d’un Rc
, ou un pointeur comptant les références.
Le Rc
surveille le nombre de personnes (ou de bouts de code) qui le regardent, et conserve la valeur jusqu’au moment où la dernière référence est supprimée.
Si vous travaillez avec un code asynchrone ou multithread, on utilisera un Arc
, ou pointeur compteur de référence atomique, mais c’est la même idée qu’un Rc
.
Tout le monde peut regarder les décorations, et on les nettoiera dès que la fête sera terminée.
Conclusion
Pour finir, vous pouvez vous poser les questions suivantes pour savoir quel type de valeur choisir :
~ | &var | &mut var | var | Rc | Arc |
---|---|---|---|---|---|
Accès en lecture ? | ✅ | ✅ | ✅ | ✅ | ✅ |
Accès en écriture ? | ❌ | ✅ | ✅ | Avec Cell /RefCell | Avec Mutex /RwLock |
Supprimé en fin de fonction ? | ❌ | ❌ | ✅ | Après suppression du dernier clone du Rc | Après suppression du dernier clone du Arc |
Utilisable par plusieurs threads ? | ❌ (sauf si durée de vie 'static ) | ❌ | Les valeurs peuvent être déplacées entre threads, mais pas accédées simultanément | ❌ | ✅ |
Les esperluettes sont l’une des parties les plus effrayantes ou les moins familières lorsque l’on vient vers Rust à partir d’un langage de plus haut niveau comme Typescript. Mais je vous promets qu’avec un peu de pratique, il sera beaucoup plus intuitif, lors de l’écriture du code, de savoir si une fonction doit prendre une référence mutable ou immuable, ou de savoir si la fonction d’une autre bibliothèque acceptera probablement une référence ou une valeur possédée. Sans avoir besoin d’un développement axé sur les esperluettes.
Pour plus de détails, vous pouvez également consulter les sections de la documentation du langage Rust sur Les références et l’emprunt et sur Qu’est-ce que la possession ?.
Evan Schwartz est ingénieur Rust principal chez Fiberplane. Il est le créateur d’Autometrics, un nouveau crate qui permet de comprendre facilement le taux d’erreur, la latence et l’utilisation en production de n’importe quelle fonction de votre code.