Les pointeurs intelligents

Un pointeur est un concept général pour une variable qui contient une adresse vers la mémoire. Cette adresse pointe vers d'autres données. Le type de pointeur le plus courant en Rust est la référence, que vous avez appris au chapitre 4. Les références sont marquées par le symbole & et empruntent la valeur sur laquelle elles pointent. Elles n'ont pas d'autres fonctionnalités que celle de pointer sur une donnée. De plus, elles n'ont aucun coût sur les performances et c'est le type de pointeur que nous utilisons le plus souvent.

Les pointeurs intelligents, d'un autre côté, sont des structures de données qui, non seulement se comportent comme un pointeur, mais ont aussi des fonctionnalités et métadonnées supplémentaires. Le concept de pointeur intelligent n'est pas propre à Rust : les pointeurs intelligents sont originaires du C++ et existent aussi dans d'autres langages. En Rust, les différents pointeurs intelligents définis dans la bibliothèque standard fournissent des fonctionnalités supplémentaires à celles des références. Un exemple que nous allons explorer dans ce chapitre est le type de pointeur intelligent compteur de références. Ce pointeur vous permet d'avoir plusieurs propriétaires d'une donnée tout en gardant une trace de leur nombre et, lorsqu'il n'y en a plus, de nettoyer cette donnée.

En Rust, qui utilise le concept de propriété et d'emprunt, une différence supplémentaire entre les références et les pointeurs intelligents est que les références sont des pointeurs qui empruntent seulement la donnée ; alors qu'au contraire, dans de nombreux cas, les pointeurs intelligents sont propriétaires des données sur lesquelles ils pointent.

Nous avons déjà rencontré quelques pointeurs intelligents au cours de ce livre, comme String et Vec<T> au chapitre 8, même si nous ne les avons pas désignés comme étant des pointeurs intelligents à ce moment-là. Ces deux types sont considérés comme des pointeurs intelligents car ils sont propriétaires de données et vous permettent de les manipuler. Ils ont aussi des métadonnées (comme leur capacité) et certaines fonctionnalités ou garanties (comme String qui s'assure que ses données soient toujours en UTF-8 valide).

Les pointeurs intelligents sont souvent implémentés en utilisant des structures. Les caractéristiques qui distinguent un pointeur intelligent d'une structure classique est que les pointeurs intelligents implémentent les traits Deref et Drop. Le trait Deref permet à une instance d'un pointeur intelligent de se comporter comme une référence afin que vous puissiez écrire du code qui fonctionne aussi bien avec des références qu'avec des pointeurs intelligents. Le trait Drop vous permet de personnaliser le code qui est exécuté lorsqu'une instance d'un pointeur intelligent sort de la portée. Dans ce chapitre, nous verrons ces deux traits et expliquerons pourquoi ils sont importants pour les pointeurs intelligents.

Vu que le motif des pointeurs intelligents est un motif de conception général fréquemment utilisé en Rust, ce chapitre ne couvrira pas tous les pointeurs intelligents existants. De nombreuses bibliothèques ont leurs propres pointeurs intelligents, et vous pouvez même écrire le vôtre. Nous allons voir les pointeurs intelligents les plus courants de la bibliothèque standard :

  • Box<T> pour l'allocation de valeurs sur le tas
  • Rc<T>, un type comptant les références, qui permet d'avoir plusieurs propriétaires
  • Ref<T> et RefMut<T>, auxquels on accède via RefCell<T>, un type qui permet d'appliquer les règles d'emprunt au moment de l'exécution plutôt qu'au moment de la compilation

En outre, nous allons voir le motif de mutabilité interne dans lequel un type immuable propose une API pour modifier une valeur interne. Nous allons aussi parler des boucles de références : comment elles peuvent provoquer des fuites de mémoire et comment les éviter.

Allons-y !