web-dev-qa-db-fra.com

Besoin d'une explication holistique sur les types comptés de cellules et de références de Rust

Il existe plusieurs types de wrapper dans la bibliothèque standard Rust:

Si je comprends bien, ce sont des enveloppes qui offrent des possibilités supplémentaires par rapport à une simple référence. Bien que je comprenne quelques notions de base, je ne peux pas voir l’ensemble du tableau.

Que font-ils exactement? Les cellules et les familles dénombrées par référence fournissent-elles des caractéristiques orthogonales ou similaires?

64
French Boiethios

Il y a deux concepts essentiels dans Rust:

  • La possession,
  • Mutabilité.

Les différents types de pointeurs (Box, Rc, Arc) concernent la propriété : ils permet de contrôler s'il y a un ou plusieurs propriétaires pour un seul objet.

Par contre, les différentes cellules (Cell, RefCell, Mutex, RwLock, AtomicXXX) sont concernées Mutabilité .


La règle fondatrice de la sécurité de Rust est aliasing XOR mutabilité. Autrement dit, un objet ne peut être muté en toute sécurité que s'il n'existe aucune référence en suspens à son intérieur.

Cette règle est généralement appliquée à la compilation par le vérificateur d'emprunt :

  • si tu as un &T, vous ne pouvez pas aussi avoir un &mut T vers le même objet dans la portée,
  • si tu as un &mut T, vous ne pouvez pas non plus avoir de référence au même objet dans la portée.

Cependant, parfois, ce n'est pas assez flexible. Parfois, vous DEVEZ (ou souhaitez) avoir la possibilité d’avoir plusieurs références au même objet tout en les transformant. Entrez le cellules.

L'idée de Cell et RefCell est de permettre la mutabilité en présence de repliement du spectre de manière contrôlée :

  • Cell empêche la formation de références à son intérieur, en évitant les références en suspens,
  • RefCell déplace l'application de aliasing XOR mutabilité = de la compilation à l'exécution].

Cette fonctionnalité est parfois décrite comme fournissant mutabilité intérieure, c’est-à-dire un objet qui semble immuable de l’extérieur (&T) peut effectivement être muté.

Lorsque cette mutabilité s'étend sur plusieurs threads, vous utiliserez plutôt Mutex, RwLock ou AtomicXXX; ils offrent les mêmes fonctionnalités:

  • AtomicXXX ne sont que Cell: pas de référence à l'intérieur, mais juste pour entrer/sortir,
  • RwLock n'est que RefCell: on peut obtenir des références à l'intérieur par des gardes ,
  • Mutex est une version simplifiée de RwLock qui ne fait pas la distinction entre un protecteur en lecture seule et un protecteur en écriture; conceptuellement semblable à un RefCell avec seulement un borrow_mut méthode.

Si vous venez d'un contexte C++:

  • Box est unique_ptr,
  • Arc est shared_ptr,
  • Rc est une version non sécurisée pour les threads de shared_ptr.

Et les cellules offrent une fonctionnalité similaire à mutable, à l'exception des garanties supplémentaires permettant d'éviter les problèmes d'aliasing; penser à Cell comme std::atomic et RefCell comme une version non thread-safe de std::shared_mutex (qui jette au lieu de bloquer si le verrou est pris).

120
Matthieu M.

Grâce à bonne réponse de Matthie , voici un diagramme pour aider les gens à trouver l'emballage qui leur convient:

+-----------+
| Ownership |
+--+--------+                              +================+
   |                         +-Static----->| T              |(1)
   |                         |             +================+
   |                         |
   |                         |             +================+
   |          +-----------+  | Local    Val| Cell<T>        |(1)
   +-Unique-->| Borrowing +--+-Dynamic---->|----------------|
   |          +-----------+  |          Ref| RefCell<T>     |(1)
   |                         |             +================+
   |                         |
   |                         |             +================+
   |                         | Threaded    | AtomicT        |(2)
   |                         +-Dynamic---->|----------------|
   |                                       | Mutex<T>       |(1)
   |                                       | RwLock<T>      |(1)
   |                                       +================+
   |
   |
   |                                       +================+
   |                         +-No--------->| Rc<T>          |
   |                         |             +================+
   | Locally  +-----------+  |
   +-Shared-->| Mutable?  +--+             +================+
   |          +-----------+  |          Val| Rc<Cell<T>>    |
   |                         +-Yes-------->|----------------|
   |                                    Ref| Rc<RefCell<T>> |
   |                                       +================+
   |
   |
   |                                       +================+
   |                         +-No--------->| Arc<T>         |
   |                         |             +================+
   | Shared   +-----------+  |
   +-Between->| Mutable?  +--+             +================+
     Threads  +-----------+  |             | Arc<AtomicT>   |(2)
                             +-Yes-------->|----------------|
                                           | Arc<Mutex<T>>  |
                                           | Arc<RwLock<T>> |
                                           +================+
  1. Dans ces cas, T peut être remplacé par Box<T>
  2. Utilisez AtomicT lorsque T est un bool ou un nombre

Pour savoir si vous devez utiliser Mutex ou RwLock, voir cette question connexe .

6
French Boiethios