Stockage de listes de valeurs avec des vecteurs

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 Storing Lists of Values With Vectors. Ce laboratoire est une partie du Rust Book. Vous pouvez pratiquer vos compétences Rust dans LabEx.

"Dans ce laboratoire, nous explorerons le type de collection Vec<T>, également appelé vecteur, qui permet de stocker des listes de valeurs du même type dans une seule structure de données."


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL rust(("Rust")) -.-> rust/ControlStructuresGroup(["Control Structures"]) rust(("Rust")) -.-> rust/FunctionsandClosuresGroup(["Functions and Closures"]) rust(("Rust")) -.-> rust/DataStructuresandEnumsGroup(["Data Structures and Enums"]) rust(("Rust")) -.-> rust/AdvancedTopicsGroup(["Advanced Topics"]) rust(("Rust")) -.-> rust/BasicConceptsGroup(["Basic Concepts"]) rust(("Rust")) -.-> rust/DataTypesGroup(["Data Types"]) rust/BasicConceptsGroup -.-> rust/variable_declarations("Variable Declarations") rust/BasicConceptsGroup -.-> rust/mutable_variables("Mutable Variables") rust/DataTypesGroup -.-> rust/integer_types("Integer Types") rust/DataTypesGroup -.-> rust/floating_types("Floating-point Types") rust/ControlStructuresGroup -.-> rust/for_loop("for Loop") 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-100406{{"Stockage de listes de valeurs avec des vecteurs"}} rust/mutable_variables -.-> lab-100406{{"Stockage de listes de valeurs avec des vecteurs"}} rust/integer_types -.-> lab-100406{{"Stockage de listes de valeurs avec des vecteurs"}} rust/floating_types -.-> lab-100406{{"Stockage de listes de valeurs avec des vecteurs"}} rust/for_loop -.-> lab-100406{{"Stockage de listes de valeurs avec des vecteurs"}} rust/expressions_statements -.-> lab-100406{{"Stockage de listes de valeurs avec des vecteurs"}} rust/method_syntax -.-> lab-100406{{"Stockage de listes de valeurs avec des vecteurs"}} rust/operator_overloading -.-> lab-100406{{"Stockage de listes de valeurs avec des vecteurs"}} end

Storing Lists of Values with Vectors

Le premier type de collection que nous allons examiner est Vec<T>, également appelé vecteur. Les vecteurs vous permettent de stocker plusieurs valeurs dans une seule structure de données qui place toutes les valeurs les unes à côté des autres en mémoire. Les vecteurs ne peuvent stocker que des valeurs du même type. Ils sont utiles lorsque vous avez une liste d'éléments, comme les lignes de texte dans un fichier ou les prix des articles dans un panier d'achat.

Creating a New Vector

Pour créer un nouveau vecteur vide, nous appelons la fonction Vec::new, comme montré dans la Liste 8-1.

let v: Vec<i32> = Vec::new();

Liste 8-1: Création d'un nouveau vecteur vide pour stocker des valeurs de type i32

Notez que nous avons ajouté une annotation de type ici. Comme nous n'insérons aucune valeur dans ce vecteur, Rust ne sait pas quel type d'éléments nous avons l'intention de stocker. Ceci est un point important. Les vecteurs sont implémentés à l'aide de génériques ; nous aborderons la manière d'utiliser les génériques avec vos propres types au Chapitre 10. Pour l'instant, sachez que le type Vec<T> fourni par la bibliothèque standard peut stocker n'importe quel type. Lorsque nous créons un vecteur pour stocker un type spécifique, nous pouvons spécifier le type entre crochets. Dans la Liste 8-1, nous avons dit à Rust que le Vec<T> dans v stockera des éléments du type i32.

Plus souvent, vous créerez un Vec<T> avec des valeurs initiales et Rust devinera le type de valeur que vous voulez stocker, de sorte que vous n'aurez rarement besoin de cette annotation de type. Rust fournit commodément le macro vec!, qui créera un nouveau vecteur qui stockera les valeurs que vous lui donnez. La Liste 8-2 crée un nouveau Vec<i32> qui stocke les valeurs 1, 2 et 3. Le type entier est i32 car c'est le type entier par défaut, comme nous l'avons discuté dans "Data Types".

let v = vec![1, 2, 3];

Liste 8-2: Création d'un nouveau vecteur contenant des valeurs

Comme nous avons donné des valeurs initiales de type i32, Rust peut deviner que le type de v est Vec<i32>, et l'annotation de type n'est pas nécessaire. Ensuite, nous examinerons comment modifier un vecteur.

Updating a Vector

Pour créer un vecteur puis y ajouter des éléments, nous pouvons utiliser la méthode push, comme montré dans la Liste 8-3.

let mut v = Vec::new();

v.push(5);
v.push(6);
v.push(7);
v.push(8);

Liste 8-3: Utilisation de la méthode push pour ajouter des valeurs à un vecteur

Comme pour toute variable, si nous voulons être en mesure de changer sa valeur, nous devons la rendre mutable à l'aide du mot clé mut, comme discuté au Chapitre 3. Les nombres que nous plaçons à l'intérieur sont tous de type i32, et Rust le déduit à partir des données, de sorte que nous n'avons pas besoin de l'annotation Vec<i32>.

Reading Elements of Vectors

Il existe deux manières de référencer une valeur stockée dans un vecteur : via l'indexation ou en utilisant la méthode get. Dans les exemples suivants, nous avons annoté les types des valeurs qui sont renvoyées par ces fonctions pour plus de clarté.

La Liste 8-4 montre les deux méthodes d'accès à une valeur dans un vecteur, avec la syntaxe d'indexation et la méthode get.

let v = vec![1, 2, 3, 4, 5];

1 let third: &i32 = &v[2];
println!("The third element is {third}");

2 let third: Option<&i32> = v.get(2);
match third  {
    Some(third) => println!("The third element is {third}"),
    None => println!("There is no third element."),
}

Liste 8-4: Utilisation de la syntaxe d'indexation et de la méthode get pour accéder à un élément dans un vecteur

Notez quelques détails ici. Nous utilisons la valeur d'index 2 pour obtenir le troisième élément [1] car les vecteurs sont indexés par numéro, en commençant à zéro. En utilisant & et [], nous obtenons une référence à l'élément à la valeur d'index. Lorsque nous utilisons la méthode get avec l'index passé en argument [2], nous obtenons un Option<&T> que nous pouvons utiliser avec match.

Rust fournit ces deux manières de référencer un élément afin que vous puissiez choisir comment le programme se comporte lorsque vous essayez d'utiliser une valeur d'index en dehors de la plage d'éléments existants. Par exemple, voyons ce qui se passe lorsque nous avons un vecteur de cinq éléments et que nous essayons ensuite d'accéder à un élément à l'index 100 avec chaque technique, comme montré dans la Liste 8-5.

let v = vec![1, 2, 3, 4, 5];

let does_not_exist = &v[100];
let does_not_exist = v.get(100);

Liste 8-5: Tentative d'accès à l'élément à l'index 100 dans un vecteur contenant cinq éléments

Lorsque nous exécutons ce code, la première méthode [] fera planter le programme car elle référence un élément qui n'existe pas. Cette méthode est la mieux adaptée lorsque vous voulez que votre programme plante si une tentative d'accès à un élément au-delà de la fin du vecteur est effectuée.

Lorsque la méthode get est passée un index qui est en dehors du vecteur, elle renvoie None sans planter. Vous utiliseriez cette méthode si l'accès à un élément au-delà de la plage du vecteur peut arriver occasionnellement dans les circonstances normales. Votre code devra alors avoir une logique pour gérer le fait d'avoir soit Some(&element) soit None, comme discuté au Chapitre 6. Par exemple, l'index pourrait provenir d'un utilisateur qui entre un nombre. S'ils entrent accidentellement un nombre trop grand et que le programme obtient une valeur None, vous pourriez dire à l'utilisateur combien d'éléments sont dans le vecteur actuel et leur donner une autre chance d'entrer une valeur valide. Cela serait plus convivial que de faire planter le programme en raison d'une erreur de frappe!

Lorsque le programme a une référence valide, le vérificateur d'emprunt applique les règles de propriété et d'emprunt (couvertes au Chapitre 4) pour vous assurer que cette référence et toute autre référence au contenu du vecteur restent valides. Rappelez-vous la règle qui stipule que vous ne pouvez pas avoir de références mutables et immuables dans la même portée. Cette règle s'applique dans la Liste 8-6, où nous avons une référence immutable au premier élément d'un vecteur et essayons d'ajouter un élément à la fin. Ce programme ne fonctionnera pas si nous essayons également de faire référence à cet élément plus tard dans la fonction.

let mut v = vec![1, 2, 3, 4, 5];

let first = &v[0];

v.push(6);

println!("The first element is: {first}");

Liste 8-6: Tentative d'ajouter un élément à un vecteur tout en maintenant une référence à un élément

La compilation de ce code entraînera cette erreur :

error[E0502]: cannot borrow `v` as mutable because it is also borrowed as
immutable
 --> src/main.rs:6:5
  |
4 |     let first = &v[0];
  |                  - immutable borrow occurs here
5 |
6 |     v.push(6);
  |     ^^^^^^^^^ mutable borrow occurs here
7 |
8 |     println!("The first element is: {first}");
  |                                      ----- immutable borrow later used here

Le code dans la Liste 8-6 peut sembler fonctionner : pourquoi une référence au premier élément devrait-elle se soucier des changements à la fin du vecteur? Cette erreur est due à la manière dont les vecteurs fonctionnent : car les vecteurs placent les valeurs les unes à côté des autres en mémoire, ajouter un nouvel élément à la fin du vecteur peut nécessiter d'allouer de nouvelles mémoires et de copier les anciens éléments dans le nouvel espace, s'il n'y a pas assez de place pour placer tous les éléments les uns à côté des autres où le vecteur est actuellement stocké. Dans ce cas, la référence au premier élément pointerait vers une mémoire désallouée. Les règles d'emprunt empêchent les programmes d'en arriver à cette situation.

Note: Pour en savoir plus sur les détails d'implémentation du type Vec<T>, consultez "The Rustonomicon" à https://doc.rust-lang.org/nomicon/vec/vec.html.

Iterating Over the Values in a Vector

Pour accéder à chaque élément d'un vecteur tour à tour, nous devrions parcourir tous les éléments plutôt que d'utiliser des indices pour accéder à un élément à la fois. La Liste 8-7 montre comment utiliser une boucle for pour obtenir des références immuables à chaque élément dans un vecteur de valeurs de type i32 et les afficher.

let v = vec![100, 32, 57];
for i in &v {
    println!("{i}");
}

Liste 8-7: Affichage de chaque élément d'un vecteur en itérant sur les éléments à l'aide d'une boucle for

Nous pouvons également itérer sur des références mutables à chaque élément dans un vecteur mutable afin de modifier tous les éléments. La boucle for dans la Liste 8-8 ajoutera 50 à chaque élément.

let mut v = vec![100, 32, 57];
for i in &mut v {
    *i += 50;
}

Liste 8-8: Itération sur des références mutables aux éléments d'un vecteur

Pour modifier la valeur à laquelle la référence mutable fait référence, nous devons utiliser l'opérateur de déréférence * pour accéder à la valeur dans i avant d'être en mesure d'utiliser l'opérateur +=. Nous en parlerons plus longuement dans "Following the Pointer to the Value".

L'itération sur un vecteur, que ce soit de manière immuable ou mutable, est sécurisée grâce aux règles du vérificateur d'emprunt. Si nous avions essayé d'insérer ou de supprimer des éléments dans les corps de boucle for des Listes 8-7 et 8-8, nous aurions eu une erreur du compilateur similaire à celle que nous avons eu avec le code de la Liste 8-6. La référence au vecteur que la boucle for détient empêche la modification simultanée de l'ensemble du vecteur.

Using an Enum to Store Multiple Types

Les vecteurs ne peuvent stocker que des valeurs du même type. Cela peut être gênant ; il existe certainement des cas d'utilisation où il est nécessaire de stocker une liste d'éléments de différents types. Heureusement, les variantes d'un enum sont définies sous le même type d'enum, donc lorsque nous avons besoin d'un type pour représenter des éléments de différents types, nous pouvons définir et utiliser un enum!

Par exemple, disons que nous voulons extraire des valeurs d'une ligne d'un tableur dans laquelle certaines des colonnes de la ligne contiennent des entiers, des nombres à virgule flottante et des chaînes de caractères. Nous pouvons définir un enum dont les variantes contiendront les différents types de valeurs, et toutes les variantes d'enum seront considérées comme le même type : celui de l'enum. Ensuite, nous pouvons créer un vecteur pour stocker cet enum et donc, finalement, stocker différents types. Nous l'avons démontré dans la Liste 8-9.

enum SpreadsheetCell {
    Int(i32),
    Float(f64),
    Text(String),
}

let row = vec![
    SpreadsheetCell::Int(3),
    SpreadsheetCell::Text(String::from("blue")),
    SpreadsheetCell::Float(10.12),
];

Liste 8-9: Définition d'un enum pour stocker des valeurs de différents types dans un seul vecteur

Rust doit savoir quels types seront dans le vecteur à la compilation afin de savoir exactement combien de mémoire sur le tas sera nécessaire pour stocker chaque élément. Nous devons également être explicites sur les types autorisés dans ce vecteur. Si Rust autorisait un vecteur à stocker n'importe quel type, il y aurait une chance qu'un ou plusieurs des types entraînent des erreurs avec les opérations effectuées sur les éléments du vecteur. Utiliser un enum plus une expression match signifie que Rust assurera au moment de la compilation que chaque cas possible est traité, comme discuté au Chapitre 6.

Si vous ne connaissez pas l'ensemble exhaustif des types que votre programme recevra à l'exécution pour les stocker dans un vecteur, la technique d'enum ne fonctionnera pas. Au lieu de cela, vous pouvez utiliser un objet trait, que nous aborderons au Chapitre 17.

Maintenant que nous avons discuté de certaines des façons les plus courantes d'utiliser les vecteurs, assurez-vous de consulter la documentation de l'API pour toutes les nombreuses méthodes utiles définies sur Vec<T> par la bibliothèque standard. Par exemple, en plus de push, une méthode pop supprime et renvoie le dernier élément.

Dropping a Vector Drops Its Elements

Comme tout autre struct, un vecteur est libéré lorsqu'il sort de portée, comme indiqué dans la Liste 8-10.

{
    let v = vec![1, 2, 3, 4];

    // faire des choses avec v
} // <- v sort de portée et est libéré ici

Liste 8-10: Montre où le vecteur et ses éléments sont supprimés

Lorsque le vecteur est supprimé, tous ses contenus sont également supprimés, ce qui signifie que les entiers qu'il contient seront nettoyés. Le vérificateur d'emprunt s'assure que toutes les références aux contenus d'un vecteur ne sont utilisées que tant que le vecteur lui-même est valide.

Passons à la prochaine collection de type : String!

Summary

Félicitations! Vous avez terminé le laboratoire Storing Lists of Values With Vectors. Vous pouvez pratiquer d'autres laboratoires dans LabEx pour améliorer vos compétences.