Menu
Coddy logo textTech

Contraintes multiples

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

Parfois, une seule borne de trait ne suffit pas. Vous pourriez avoir besoin d'un type générique qui peut à la fois être affiché et fournir un résumé. Rust vous permet d'exiger plusieurs traits en utilisant la syntaxe +.

Voici comment spécifier qu'un type doit implémenter deux traits :

use std::fmt::Display;

trait Summary {
    fn summarize(&self) -> String;
}

fn announce<T: Display + Summary>(item: T) {
    println!("Breaking news: {}", item);
    println!("Summary: {}", item.summarize());
}

La contrainte T: Display + Summary signifie que « T doit implémenter à la fois Display et Summary ». À l'intérieur de la fonction, vous pouvez utiliser les fonctionnalités des deux traits — l'affichage avec {} (provenant de Display) et l'appel à summarize() (provenant de Summary).

Vous pouvez enchaîner autant de traits que nécessaire :

fn process<T: Display + Summary + Clone>(item: T) {
    // Peut afficher, résumer ET cloner
}

Ce pattern est essentiel lorsque votre fonction repose sur plusieurs comportements. Plutôt que d'accepter n'importe quel type en espérant que cela fonctionne, vous déclarez explicitement les capacités exactes requises — et le compilateur l'impose au moment de la compilation.

challenge icon

Défi

Facile

Construisons un système d'inspection de produits qui exige que les articles possèdent plusieurs capacités ! Vous allez créer une fonction générique qui n'accepte que les types implémentant à la fois un trait personnalisé et un trait standard, démontrant ainsi comment la syntaxe + combine plusieurs bornes (bounds).

Vous organiserez votre code sur deux fichiers :

  • product.rs : Définissez un trait public Inspectable avec une méthode appelée inspect qui prend &self et retourne un String contenant les détails de l'inspection. Créez ensuite une structure publique Gadget avec des champs publics name (String) et serial (u32). Votre Gadget doit implémenter à la fois Inspectable (retournant "Inspecting: {}" avec le nom) et std::fmt::Display (formaté comme "{} (SN: {})"). Enfin, créez une fonction générique publique appelée full_report qui accepte n'importe quel type T implémentant à la fois Display et Inspectable. Cette fonction doit imprimer deux lignes : d'abord l'article en utilisant le formateur {}, puis le résultat de l'appel à inspect().
  • main.rs : Importez votre module product et créez une instance de Gadget en utilisant les entrées fournies. Appelez full_report avec votre gadget pour montrer qu'il satisfait aux exigences des deux traits.

La puissance des bornes multiples est que votre fonction full_report peut utiliser les capacités des deux traits — afficher l'article proprement ET obtenir les détails de l'inspection — le tout garanti au moment de la compilation.

Votre sortie devrait afficher à la fois le format d'affichage et le résultat de l'inspection :

{name} (SN: {serial})
Inspecting: {name}

Par exemple, avec les entrées Smartwatch et 98765 :

Smartwatch (SN: 98765)
Inspecting: Smartwatch

Vous recevrez deux entrées : le nom du gadget et le numéro de série (à analyser comme un u32).

Aide-mémoire

Vous pouvez exiger plusieurs limites de trait (trait bounds) sur un type générique en utilisant la syntaxe + :

fn function_name<T: Trait1 + Trait2>(item: T) {
    // Peut utiliser les capacités des deux traits
}

Exemple avec Display et un trait personnalisé :

use std::fmt::Display;

trait Summary {
    fn summarize(&self) -> String;
}

fn announce<T: Display + Summary>(item: T) {
    println!("Breaking news: {}", item);
    println!("Summary: {}", item.summarize());
}

Vous pouvez enchaîner plusieurs traits selon vos besoins :

fn process<T: Display + Summary + Clone>(item: T) {
    // Peut afficher, résumer ET cloner
}

Le compilateur impose que le type implémente tous les traits spécifiés au moment de la compilation.

Essayez vous-même

mod product;

use product::{Gadget, full_report};

fn main() {
    // Lire les entrées
    let mut name = String::new();
    std::io::stdin().read_line(&mut name).expect("Failed to read line");
    let name = name.trim().to_string();
    
    let mut serial_input = String::new();
    std::io::stdin().read_line(&mut serial_input).expect("Failed to read line");
    let serial: u32 = serial_input.trim().parse().expect("Failed to parse serial");
    
    // TODO: Créer une instance de Gadget avec le nom et le numéro de série
    
    // TODO: Appeler full_report avec votre gadget
}
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