Structures génériques
Fait partie de la section Programmation Orientée Objet du Journey Rust de Coddy. Leçon 24 sur 61.
Jusqu’à présent, chaque struct que tu as créée avait des champs avec des types spécifiques et concrets. Un Pet a un nom de type String, un Rectangle a des dimensions de type f64. Mais que faire si tu veux créer une struct capable de contenir any type de données ?
C’est là qu’interviennent les génériques. Les génériques vous permettent d’écrire du code flexible et réutilisable qui fonctionne avec plusieurs types. Au lieu de coder en dur un type spécifique, vous utilisez un espace réservé, généralement T, qui est remplacé par un type concret lorsque vous utilisez la structure.
Voici une structure enveloppe simple qui peut contenir n’importe quel type :
struct Wrapper<T> {
value: T,
}
Le <T> après le nom de la structure déclare un paramètre de type générique. Dans la structure, T sert de substitut au type que tu utiliseras réellement. Lorsque tu crées une instance, Rust déduit le type concret :
let int_wrapper = Wrapper { value: 42 }; // T est i32
let float_wrapper = Wrapper { value: 3.14 }; // T est f64
let text_wrapper = Wrapper { value: "hello" }; // T est &str
Chacun de ces éléments est un type concret différent, Wrapper<i32>, Wrapper<f64> et Wrapper<&str>, mais ils partagent tous la même définition de struct. Cela évite d’avoir à écrire des structs IntWrapper, FloatWrapper et StringWrapper distincts qui font essentiellement la même chose.
La lettre T n’est qu’une convention (abréviation de « type »). Tu pourrais utiliser n’importe quel identifiant valide, mais T est la convention standard en Rust et dans la plupart des autres langages prenant en charge les génériques.
Défi
FacileConstruisons un conteneur générique capable de contenir n’importe quel type de valeur ! Vous allez créer une structure Container qui montre comment les génériques rendent votre code flexible et réutilisable.
Vous organiserez votre code sur deux fichiers :
container.rs: définissez une structure générique publique appeléeContainer<T>avec un seul champ publicitemde typeT. Ce conteneur doit pouvoir contenir n’importe quel type : des entiers, des nombres à virgule flottante, des chaînes de caractères ou quoi que ce soit d’autre.main.rs: importez votre module de conteneur et démontrez sa flexibilité en créant des conteneurs qui contiennent différents types de données. Vous créerez trois conteneurs et afficherez le contenu de chacun.
Dans votre fichier principal, créez les conteneurs suivants :
- Un conteneur contenant une valeur entière (la première entrée, analysée comme un
i32) - Un conteneur contenant une valeur à virgule flottante (la deuxième entrée, analysée comme un
f64) - Un conteneur contenant une chaîne de caractères (la troisième entrée, conservée en tant que
String)
Votre sortie doit afficher le contenu de chaque conteneur dans ce format :
Integer container: {value}
Float container: {value}
String container: {value}Par exemple, avec les entrées 42, 3.14 et hello :
Integer container: 42
Float container: 3.14
String container: helloRemarquez comment la même définition de Container<T> fonctionne pour les trois types différents : c’est là toute la puissance des génériques !
Vous recevrez trois entrées : une valeur entière, une valeur à virgule flottante et une chaîne de caractères.
Essayez vous-même
mod container;
use container::Container;
fn main() {
// Lire les trois entrées
let mut input1 = String::new();
std::io::stdin().read_line(&mut input1).expect("Failed to read line");
let int_value: i32 = input1.trim().parse().expect("Failed to parse integer");
let mut input2 = String::new();
std::io::stdin().read_line(&mut input2).expect("Failed to read line");
let float_value: f64 = input2.trim().parse().expect("Failed to parse float");
let mut input3 = String::new();
std::io::stdin().read_line(&mut input3).expect("Failed to read line");
let string_value = input3.trim().to_string();
// TODO: Create three containers using Container<T>
// 1. An integer container holding int_value
// 2. A float container holding float_value
// 3. A string container holding string_value
// TODO: Afficher le contenu de chaque conteneur dans le format requis
// Integer container: {value}
// Float container: {value}
// String container: {value}
}
Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Programmation Orientée Objet
1Méthodes et comportement
Introduction aux blocs d'implémentationLe paramètre SelfMéthodes mutablesFonctions associéesBlocs d'implémentation multiplesChaînage de méthodesRécapitulatif - Actions sur le rectangle4Projet : Animal virtuel
Définir l'animalNourrir l'animal7Traits standards
Le trait DebugLe trait DisplayClone et CopyLes traits d'égalitéRécapitulatif - Point affichable10Projet : Système de documents
Le trait DrawComposant texte2Encapsulation et modules
Bases des modulesLe mot-clé publicChamps privésGettersSettersRécapitulatif - Casier sécurisé5Généricité
Structures génériquesMéthodes génériquesPlusieurs types génériquesFonctions génériquesRécapitulatif - Point de coordonnées8Les Traits comme contraintes
Syntaxe des contraintes de traitContraintes multiplesLa clause whereRetourner des types avec des traitsRécapitulatif - Afficheur générique11Design Patterns en Rust
Le pattern NewtypeCompositionLe trait DropFrom et IntoRécapitulatif - Mock de Smart Pointer3Enums avancées
Enums avec donnéesMéthodes sur les EnumsMatching des variantes de donnéesL'Enum Option revisitéeRécapitulatif - Enum Shape6Définition des Traits
Qu'est-ce qu'un Trait ?Implémenter des TraitsImplémentations par défautRedéfinir les comportements par défautTraits avec paramètresRécapitulatif - Lecteur multimédiaEntraînez-vous par vous-même : Compilateur Rust en ligne