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.