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 tasRc<T>
, un type comptant les références, qui permet d'avoir plusieurs propriétairesRef<T>
etRefMut<T>
, auxquels on accède viaRefCell<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 !