Menu
Coddy logo textTech

Structures génériques

Fait partie de la section Object Oriented Programming du Journey Rust de Coddy — leçon 24 sur 61.

Jusqu'à présent, chaque structure que vous avez créée possédait 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 se passe-t-il si vous voulez créer une structure capable de contenir n'importe quel 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 wrapper 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. À l'intérieur de la structure, T sert de substitut pour n'importe quel type que vous utiliserez réellement. Lorsque vous créez 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 d'entre eux est un type concret différent — Wrapper<i32>, Wrapper<f64> et Wrapper<&str> — mais ils partagent tous la même définition de structure. Cela élimine le besoin d'écrire des structures IntWrapper, FloatWrapper et StringWrapper distinctes qui font essentiellement la même chose.

La lettre T est simplement une convention (abrégé de « Type »). Vous pourriez utiliser n'importe quel identifiant valide, mais T est standard en Rust et dans la plupart des autres langages avec des génériques.

challenge icon

Défi

Facile

Construisons un conteneur générique capable de contenir n'importe quel type de valeur ! Vous allez créer une structure Container qui dé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ée Container<T> avec un seul champ public item de type T. Ce conteneur doit pouvoir contenir n'importe quel type — entiers, flottants, chaînes de caractères ou n'importe quoi 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 ce que chacun contient.

Dans votre fichier principal, créez les conteneurs suivants :

  1. Un conteneur contenant une valeur entière (la première entrée, analysée comme i32)
  2. Un conteneur contenant une valeur flottante (la deuxième entrée, analysée comme f64)
  3. Un conteneur contenant une chaîne de caractères (la troisième entrée, conservée comme 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: hello

Remarquez 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 flottante et une chaîne de caractères.

Aide-mémoire

Les génériques vous permettent de créer des structures (structs) pouvant contenir n'importe quel type de données en utilisant un paramètre de type générique.

Pour déclarer une structure générique, utilisez des chevrons avec un paramètre de type (par convention T) :

struct Wrapper<T> {
    value: T,
}

Le <T> déclare un paramètre de type générique, et T à l'intérieur de la structure agit comme un substitut pour le type réel.

Lors de la création d'instances, 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

Chaque instance devient un type concret différent (Wrapper<i32>, Wrapper<f64>, Wrapper<&str>) tout en partageant la même définition de structure.

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: Créer trois conteneurs en utilisant Container<T>
    // 1. Un conteneur d'entiers contenant int_value
    // 2. Un conteneur de flottants contenant float_value
    // 3. Un conteneur de chaînes de caractères contenant string_value

    // TODO: Afficher le contenu de chaque conteneur dans le format requis
    // Conteneur d'entiers : {value}
    // Conteneur de flottants : {value}
    // Conteneur de chaînes de caractères : {value}
}
quiz iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Object Oriented Programming