Etendre la concurrence avec les traits Sync
et Send
Curieusement, le langage Rust a très peu de fonctionnalités de concurrence. La plupart des fonctionnalités de concurrence que nous avons vues précédemment dans ce chapitre font partie de la bibliothèque standard, pas du langage. Vos options pour gérer la concurrence ne sont pas limitées à celles du langage ou de la bibliothèque standard ; vous pouvez aussi écrire vos propres fonctionnalités de concurrence ou utiliser celles qui ont été écrites par d'autres.
Cependant, deux concepts de concurrence sont intégrés dans le langage : les
traits Sync
et Send
de std::marker
.
Permettre le transfert de possession entre les tâches avec Send
Le trait Send
indique que la possession des valeurs du type qui implémente
Send
peut être transféré entre plusieurs tâches. Presque tous les types de
Rust implémentent Send
, mais il subsiste quelques exceptions, comme Rc<T>
:
il ne peut pas implémenter Send
car si vous clonez une valeur Rc<T>
et que
vous essayez de transférer la possession de ce clone à une autre tâche, les
deux tâches peuvent modifier le compteur de référence en même temps. Pour cette
raison, Rc<T>
n'est prévu que pour une utilisation dans des situations qui
ne nécessitent qu'une seule tâche et pour lesquelles vous n'avez pas besoin de payer
le surcoût sur la performance induit par la sureté de fonctionnement multi tâches.
Toutefois, le système de type et de traits liés de Rust garantit que vous ne
pourrez jamais envoyer accidentellement en toute insécurité une valeur Rc<T>
entre des tâches. Lorsque nous avons essayé de faire cela dans l'encart 16-14,
nous avons obtenu l'erreur
the trait Send is not implemented for Rc<Mutex<i32>>
. Lorsque nous l'avons
changé pour un Arc<T>
, qui implémente Send
, le code s'est compilé.
Tous les types composés entièrement d'autres types qui implémentent Send
sont
automatiquement marqués comme Send
eux-aussi. Presque tous les types
primitifs sont Send
, à part les pointeurs bruts, ce que nous verrons au
chapitre 19.
Permettre l'accès à plusieurs tâches avec Sync
Le trait Sync
indique qu'il est sûr d'avoir une référence dans plusieurs
tâches vers le type qui implémente Sync
. Autrement dit, n'importe quel type
T
implémente Sync
si &T
(une référence immuable vers T
) implémente
Send
, ce qui signifie que la référence peut être envoyée en toute sécurité à
une autre tâche. De la même manière que Send
, les types primitifs
implémentent Sync
, et les types composés entièrement d'autres types qui
implémentent Sync
sont eux-mêmes Sync
.
Le pointeur intelligent Rc<T>
n'implémente pas non plus Sync
pour les mêmes
raisons qu'il n'implémente pas Send
. Le type RefCell<T>
(que nous avons vu
au chapitre 15) et la famille liée aux types Cell<T>
n'implémentent pas Sync
.
L'implémentation du vérificateur d'emprunt que RefCell<T>
met en oeuvre à l'exécution
n'est pas sûre pour le multi tâches. Le pointeur intelligent Mutex<T>
implémente Sync
et peut être utilisé pour partager l'accès entre plusieurs
tâches, comme vous l'avez vu dans la section précédente.
Implémenter manuellement Send
et Sync
n'est pas sûr
Comme les types qui sont constitués de types implémentant les traits Send
et
Sync
sont automatiquement des Send
et Sync
, nous n'avons pas à
implémenter manuellement ces traits. Comme ce sont des traits de marquage, ils
n'ont même pas de méthodes à implémenter. Ils sont uniquement utiles pour
appliquer les règles de concurrence.
L'implémentation manuelle de ces traits implique de faire du code Rust non
sécurisé. Nous allons voir le code Rust non sécurisé dans le chapitre 19 ; pour
l'instant l'information à retenir est que construire de nouveaux types
pour la concurrence constitués d'éléments qui n'implémentent pas Send
et
Sync
nécessite une réflexion approfondie pour respecter les garanties de
sécurité. “The Rustonomicon” contient plus d'informations à propos de
ces garanties et de la façon de les faire appliquer.
Résumé
Ce n'est pas la dernière fois que vous allez rencontrer de la concurrence dans ce livre : le projet du chapitre 20 va utiliser les concepts de ce chapitre dans une situation plus réaliste que les petits exemples que nous avons utilisés ici.
Nous l'avons dit précédemment, comme les outils pour gérer la concurrence de Rust ne sont pas directement intégrés dans le langage, de nombreuses solutions pour de la concurrence sont implémentées dans des crates. Elles évoluent plus rapidement que la bibliothèque standard, donc assurez-vous de rechercher en ligne des crates modernes et à la pointe de la technologie à utiliser dans des situations multitâches.
La bibliothèque standard de Rust fournit les canaux pour l'envoi de messages et
les types de pointeurs intelligents, comme Mutex<T>
et Arc<T>
, qui sont
sûrs à utiliser en situation de concurrence. Le système de type et le
vérificateur d'emprunt sont là pour s'assurer que le code utilisé dans ces
solutions ne vont pas conduire à des situations de concurrence ou utiliser des
références qui ne sont plus en vigueur. Une fois que votre code se compile,
vous pouvez être assuré qu'il fonctionnera bien sur plusieurs tâches sans avoir
les genres de bogues difficiles à traquer qui sont monnaie courante dans les
autres langages. Le développement en concurrence est un domaine qui ne devrait
plus faire peur : lancez-vous et utilisez la concurrence dans vos programmes
sans crainte !
Au chapitre suivant, nous allons voir des techniques adaptées pour modéliser des problèmes et structurer votre solution au fur et à mesure que vos programmes en Rust grandissent. De plus, nous analyserons les liens qui peuvent exister entre les idées de Rust et celles avec lesquelles vous êtes peut-être familier en programmation orientée objet.