Cours de programmation séquentielle

Traits

Orestis Malaspinas

Traits

Généralités

  • Un Trait permet de dire au compilateur quelles fonctionalités un type doit posséder.
  • Permet de conceptualiser les foncionalités partagées entre types.
  • On peut contraindre un type générique à avoir un certain trait (trait bound).

    
      fn max<T: PartialOrd>(a: T, b: T) -> T {
          if a > b { a } else { b } // si T ne peut pas être ordonné, "<" et ">" n'existent pas
      }                             
      
  • Similaire aux interfaces de certains langages.

Définition d’un trait

  • Dans un trait sont regroupées des méthodes qu’un type peut appeler.
  • Dans leur définition on ne connaît que leurs signatures.
  • Exemple:

    
      trait Animal { // définition d'un trait
          fn cri(&self) -> String;
    
          fn nom(&self) -> &String;
      } // le nombre de méthode est arbitraire
      

Implémentation d’un trait pour un type (1/2)

  • Tout type qui implémentera un trait devra implémenter toutes ses méthodes.
  • Le compilateur saura que tout type implémentant un trait peut appeler ses méthodes.

    
      trait Animal { // définition d'un trait
          fn cri(&self) -> String;
    
          fn nom(&self) -> &String;
      } // le nombre de méthode est arbitraire
    
      struct Chien {
          nom: String,
      }
    
      impl Animal for Chien { // le bloc où les méthodes du trait sont implémentées
          fn cri(&self) -> String {
              String::from("Ouaf!")
          }
    
          fn nom(&self) -> &String {
              &self.nom
          }
      }
    
      fn main() {
          let chien = Chien{ nom: String::from("Jack")};
          println!("Le cri de {} est {}", chien.nom(), chien.cri());
      }
      

Implémentation d’un trait pour un type (2/2)


trait Animal { // définition d'un trait
    fn cri(&self) -> String;

    fn nom(&self) -> &String;
} // le nombre de méthode est arbitraire

struct Chien {
    nom: String,
}

impl Animal for Chien { // le bloc où les méthodes du trait sont implémentées
    fn cri(&self) -> String {
        String::from("Ouaf")
    }

    fn nom(&self) -> &String {
        &self.nom
    }
}

struct Chat {
    nom: String,
}

impl Animal for Chat { // le bloc où les méthodes du trait sont implémentées
    fn cri(&self) -> String {
        String::from("Miaou")
    }

    fn nom(&self) -> &String {
        &self.nom
    }
}

fn main() {
    let chien = Chien{ nom: String::from("Jack")};
    println!("Le cri de {} est {}.", chien.nom(), chien.cri());

    let chat = Chat{ nom: String::from("Rémy")};
    println!("Le cri de {} est {}.", chat.nom(), chat.cri());
}

Implémentations par défaut

  • Il est pratique d’avoir des implémentations par défaut (les mêmes pour tous les types implémentant un trait).

    
      trait Animal { // définition d'un trait
          fn cri(&self) -> String;
    
          fn cri_puissant(&self) -> String {
              self.cri().to_uppercase()
          }
      } // le nombre de méthode est arbitraire
    
      struct Chien { }
    
      impl Animal for Chien {
          fn cri(&self) -> String {
              String::from("Ouaf")
          }
      }
    
      struct Chat { }
    
      impl Animal for Chat { // le bloc où les méthodes du trait sont implémentées
          fn cri(&self) -> String {
              String::from("Miaou")
          }
      }
    
      fn main() {
          let chien = Chien{};
          println!("Le cri du chien est {} et son cri puissant est {}.",
              chien.cri(), chien.cri_puissant());
    
          let chat = Chat{};
          println!("Le cri du chat est {} et son cri puissant est {}.",
              chat.cri(), chat.cri_puissant());
      }
      

Trait bound (“contrainte de trait”)

  • Lors de la définition d’un générique, on peut dire au compilateur si le type implémente un trait.

    
      // Cette fonction ne peut pas foncitonner pour tous les types
      // T implémente PartialEq (les opérateurs <, >)
      fn max<T: PartialOrd>(a: T, b: T) -> T {
          if a > b { a } else { b } // si on peut pas comparer a et b
      }                             // cela ne peut pas compiler, d'où
                                  // le PartialOrd
      fn main() {
          let a = 1;
          let b = 7;
    
          println!("De {} et {}, {} est le plus grand.", a, b, max(a,b));
    
          let a = 1.5;
          let b = 7.5;
    
          println!("De {} et {}, {} est le plus grand.", a, b, max(a,b));
      }
      

Règle pour l’implémentation d’un trait

  • On peut implémenter un trait seulement si le trait ou le type est local à notre librairie (crate).
  • Impossible de réimplémenter l’addition pour les f64 (le trait Add et le type f64) sont dans la librairie standard, donc pas dans une librairie locale.