Rc<T>, le pointeur intelligent à comptage de références

RustRustBeginner
Pratiquer maintenant

This tutorial is from open-source community. Access the source code

💡 Ce tutoriel est traduit par l'IA à partir de la version anglaise. Pour voir la version originale, vous pouvez cliquer ici

Introduction

Bienvenue dans Rc, le pointeur intelligent à comptage de références. Ce laboratoire est une partie du Rust Book. Vous pouvez pratiquer vos compétences Rust dans LabEx.

Dans ce laboratoire, nous allons explorer l'utilisation de Rc (comptage de références) en Rust pour permettre à plusieurs propriétaires d'une valeur en suivant le nombre de références à cette valeur et en veillant à ce qu'elle ne soit nettoyée que lorsqu'il n'y a plus de propriétaires.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL rust(("Rust")) -.-> rust/AdvancedTopicsGroup(["Advanced Topics"]) rust(("Rust")) -.-> rust/BasicConceptsGroup(["Basic Concepts"]) rust(("Rust")) -.-> rust/DataTypesGroup(["Data Types"]) rust(("Rust")) -.-> rust/FunctionsandClosuresGroup(["Functions and Closures"]) rust(("Rust")) -.-> rust/DataStructuresandEnumsGroup(["Data Structures and Enums"]) rust/BasicConceptsGroup -.-> rust/variable_declarations("Variable Declarations") rust/DataTypesGroup -.-> rust/integer_types("Integer Types") rust/FunctionsandClosuresGroup -.-> rust/function_syntax("Function Syntax") rust/FunctionsandClosuresGroup -.-> rust/expressions_statements("Expressions and Statements") rust/DataStructuresandEnumsGroup -.-> rust/method_syntax("Method Syntax") rust/AdvancedTopicsGroup -.-> rust/operator_overloading("Traits for Operator Overloading") subgraph Lab Skills rust/variable_declarations -.-> lab-100434{{"Rc, le pointeur intelligent à comptage de références"}} rust/integer_types -.-> lab-100434{{"Rc, le pointeur intelligent à comptage de références"}} rust/function_syntax -.-> lab-100434{{"Rc, le pointeur intelligent à comptage de références"}} rust/expressions_statements -.-> lab-100434{{"Rc, le pointeur intelligent à comptage de références"}} rust/method_syntax -.-> lab-100434{{"Rc, le pointeur intelligent à comptage de références"}} rust/operator_overloading -.-> lab-100434{{"Rc, le pointeur intelligent à comptage de références"}} end

Rc<T>{=html}, le pointeur intelligent à comptage de références

Dans la majorité des cas, la propriété est claire : vous savez exactement quelle variable possède une valeur donnée. Cependant, il existe des cas où une seule valeur peut avoir plusieurs propriétaires. Par exemple, dans les structures de données de graphe, plusieurs arêtes peuvent pointer vers le même nœud, et ce nœud est conceptuellement propriété de toutes les arêtes qui le pointent. Un nœud ne devrait pas être nettoyé à moins qu'il n'ait aucune arête qui le pointe et donc qu'il n'ait aucun propriétaire.

Vous devez activer l'appartenance multiple explicitement en utilisant le type Rust Rc<T>, qui est une abréviation de comptage de références. Le type Rc<T> suit le nombre de références à une valeur pour déterminer si la valeur est encore en usage. Si le nombre de références à une valeur est égal à zéro, la valeur peut être nettoyée sans que les références deviennent invalides.

Imaginez Rc<T> comme une télévision dans une salle de séjour. Quand une personne entre pour regarder la télévision, elle l'allume. D'autres peuvent entrer dans la pièce et regarder la télévision. Quand la dernière personne quitte la pièce, elle éteint la télévision car elle n'est plus utilisée. Si quelqu'un éteint la télévision tandis que d'autres sont encore en train de la regarder, il y aurait un tollé des autres téléspectateurs!

Nous utilisons le type Rc<T> lorsque nous voulons allouer des données sur le tas pour que plusieurs parties de notre programme puissent les lire et que nous ne puissions pas déterminer à la compilation laquelle des parties utilisera les données en dernier. Si nous savions laquelle des parties terminerait en dernier, nous pourrions simplement rendre cette partie propriétaire des données, et les règles normales de propriété appliquées à la compilation entreraient en vigueur.

Notez que Rc<T> n'est destiné qu'à être utilisé dans des scénarios mono-fil. Lorsque nous aborderons la concurrence au chapitre 16, nous verrons comment effectuer le comptage de références dans les programmes multithreadés.

Utilisation de Rc<T>{=html} pour partager des données

Revoyons notre exemple de liste cons dans la Liste 15-5. Rappelez-vous que nous l'avons défini en utilisant Box<T>. Cette fois, nous allons créer deux listes qui partagent toutes deux la propriété d'une troisième liste. Conceptuellement, cela ressemble à la Figure 15-3.

Figure 15-3 : Deux listes, b et c, partageant la propriété d'une troisième liste, a

Nous allons créer la liste a qui contient 5 puis 10. Ensuite, nous allons créer deux autres listes : b qui commence par 3 et c qui commence par 4. Les listes b et c continueront ensuite jusqu'à la première liste a contenant 5 et 10. En d'autres termes, les deux listes partageront la première liste contenant 5 et 10.

Essayons d'implémenter ce scénario en utilisant notre définition de List avec Box<T>. Cela ne fonctionnera pas, comme le montre la Liste 15-17.

Nom de fichier : src/main.rs

enum List {
    Cons(i32, Box<List>),
    Nil,
}

use crate::List::{Cons, Nil};

fn main() {
    let a = Cons(5, Box::new(Cons(10, Box::new(Nil))));
  1 let b = Cons(3, Box::new(a));
  2 let c = Cons(4, Box::new(a));
}

Liste 15-17 : Demonstration du fait que nous ne sommes pas autorisés à avoir deux listes utilisant Box<T> qui tentent de partager la propriété d'une troisième liste

Lorsque nous compilons ce code, nous obtenons cette erreur :

error[E0382]: use of moved value: `a`
  --> src/main.rs:11:30
   |
9  |     let a = Cons(5, Box::new(Cons(10, Box::new(Nil))));
   |         - move occurs because `a` has type `List`, which
does not implement the `Copy` trait
10 |     let b = Cons(3, Box::new(a));
   |                              - value moved here
11 |     let c = Cons(4, Box::new(a));
   |                              ^ value used here after move

Les variantes Cons possèdent les données qu'elles contiennent, donc lorsque nous créons la liste b [1], a est déplacé dans b et b possède a. Ensuite, lorsque nous essayons d'utiliser a à nouveau lors de la création de c [2], nous ne sommes pas autorisés car a a été déplacé.

Nous pourrions modifier la définition de Cons pour qu'elle contienne des références à la place, mais alors nous devrions spécifier des paramètres de durée de vie. En spécifiant des paramètres de durée de vie, nous spécifierions que chaque élément de la liste vivra au moins aussi longtemps que la liste entière. C'est le cas pour les éléments et les listes de la Liste 15-17, mais pas dans tous les scénarios.

Au lieu de cela, nous allons modifier notre définition de List pour utiliser Rc<T> à la place de Box<T>, comme le montre la Liste 15-18. Chaque variante Cons contiendra désormais une valeur et un Rc<T> pointant vers une List. Lorsque nous créons b, au lieu de prendre la propriété de a, nous allons cloner le Rc<List> que a contient, augmentant ainsi le nombre de références d'un à deux et laissant a et b partager la propriété des données dans ce Rc<List>. Nous allons également cloner a lors de la création de c, augmentant le nombre de références de deux à trois. Chaque fois que nous appelons Rc::clone, le compte de références des données à l'intérieur du Rc<List> augmentera, et les données ne seront nettoyées que s'il n'y a plus de références à elles.

Nom de fichier : src/main.rs

enum List {
    Cons(i32, Rc<List>),
    Nil,
}

use crate::List::{Cons, Nil};
1 use std::rc::Rc;

fn main() {
  2 let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
  3 let b = Cons(3, Rc::clone(&a));
  4 let c = Cons(4, Rc::clone(&a));
}

Liste 15-18 : Une définition de List qui utilise Rc<T>

Nous devons ajouter une instruction use pour amener Rc<T> dans la portée [1] car il n'est pas dans le préambule. Dans main, nous créons la liste contenant 5 et 10 et la stockons dans un nouveau Rc<List> dans a [2]. Ensuite, lorsque nous créons b [3] et c [4], nous appelons la fonction Rc::clone et passons une référence au Rc<List> dans a en tant qu'argument.

Nous aurions pu appeler a.clone() plutôt que Rc::clone(&a), mais la convention de Rust est d'utiliser Rc::clone dans ce cas. L'implémentation de Rc::clone ne fait pas une copie profonde de toutes les données comme le font la plupart des implémentations de clone des types. L'appel à Rc::clone n'incremente que le compte de références, ce qui ne prend pas beaucoup de temps. Les copies profondes de données peuvent prendre beaucoup de temps. En utilisant Rc::clone pour le comptage de références, nous pouvons distinguer visuellement entre les types de clones de copie profonde et les types de clones qui augmentent le compte de références. Lorsque nous cherchons des problèmes de performance dans le code, nous n'avons besoin de considérer que les clones de copie profonde et pouvons ignorer les appels à Rc::clone.

Cloner un Rc<T>{=html} augmente le compte de références

Modifions notre exemple fonctionnel de la Liste 15-18 pour voir les comptes de références changer au fur et à mesure que nous créons et supprimons des références au Rc<List> dans a.

Dans la Liste 15-19, nous modifierons main pour qu'il ait une portée interne autour de la liste c ; puis nous pourrons voir comment le compte de références change lorsque c sort de portée.

Nom de fichier : src/main.rs

--snip--

fn main() {
    let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
    println!(
        "count after creating a = {}",
        Rc::strong_count(&a)
    );
    let b = Cons(3, Rc::clone(&a));
    println!(
        "count after creating b = {}",
        Rc::strong_count(&a)
    );
    {
        let c = Cons(4, Rc::clone(&a));
        println!(
            "count after creating c = {}",
            Rc::strong_count(&a)
        );
    }
    println!(
        "count after c goes out of scope = {}",
        Rc::strong_count(&a)
    );
}

Liste 15-19 : Affichage du compte de références

À chaque étape du programme où le compte de références change, nous affichons le compte de références, que nous obtenons en appelant la fonction Rc::strong_count. Cette fonction est nommée strong_count plutôt que count car le type Rc<T> a également un weak_count ; nous verrons à quoi sert weak_count dans "Prévenir les cycles de références en utilisant Weak<T>{=html}".

Ce code affiche ceci :

count after creating a = 1
count after creating b = 2
count after creating c = 3
count after c goes out of scope = 2

Nous pouvons voir que le Rc<List> dans a a un compte de références initial de 1 ; puis chaque fois que nous appelons clone, le compte augmente de 1. Lorsque c sort de portée, le compte diminue de 1. Nous n'avons pas besoin d'appeler une fonction pour diminuer le compte de références comme nous devons appeler Rc::clone pour augmenter le compte de références : l'implémentation du trait Drop diminue automatiquement le compte de références lorsqu'une valeur Rc<T> sort de portée.

Ce que nous ne pouvons pas voir dans cet exemple est que lorsque b puis a sortent de portée à la fin de main, le compte est alors 0, et le Rc<List> est entièrement nettoyé. Utiliser Rc<T> permet à une seule valeur d'avoir plusieurs propriétaires, et le compte assure que la valeur reste valide tant qu'un des propriétaires existe encore.

Via des références immuables, Rc<T> vous permet de partager des données entre plusieurs parties de votre programme pour lecture seulement. Si Rc<T> vous permettait également d'avoir plusieurs références mutables, vous pourriez violer l'une des règles d'emprunt discutées au chapitre 4 : plusieurs emprunts mutables au même endroit peuvent entraîner des courses de données et des inconvénients. Mais être capable de modifier des données est très utile! Dans la section suivante, nous aborderons le modèle de mutabilité interne et le type RefCell<T> que vous pouvez utiliser en conjonction avec un Rc<T> pour travailler avec cette restriction d'immuabilité.

Sommaire

Félicitations! Vous avez terminé le laboratoire Rc, le pointeur intelligent à comptage de références. Vous pouvez pratiquer d'autres laboratoires dans LabEx pour améliorer vos compétences.