Generics
1. Fonctions génériques
// Sans génériques — dupliqué
fn plus_grand_i32(a: i32, b: i32) -> i32 {
if a > b { a } else { b }
}
fn plus_grand_f64(a: f64, b: f64) -> f64 {
if a > b { a } else { b }
}
// Avec génériques — un seul code
fn plus_grand<T: PartialOrd>(a: T, b: T) -> T {
if a > b { a } else { b }
}
println!("{}", plus_grand(5, 10)); // 10
println!("{}", plus_grand(3.14, 2.71)); // 3.14
println!("{}", plus_grand('a', 'z')); // z
2. Structs et enums génériques
// Struct générique
struct Paire<T> {
premier: T,
second: T,
}
impl<T> Paire<T> {
fn new(premier: T, second: T) -> Self {
Paire { premier, second }
}
}
// Plusieurs paramètres de type
struct Couple<T, U> {
gauche: T,
droite: U,
}
let c = Couple { gauche: 42, droite: "bonjour" };
// Enums génériques — tu les connais déjà !
enum Option<T> {
Some(T),
None,
}
enum Result<T, E> {
Ok(T),
Err(E),
}
3. Trait bounds
3.1 Syntaxe inline
// T doit implémenter Display
fn afficher<T: std::fmt::Display>(valeur: T) {
println!("{valeur}");
}
// T doit implémenter Display + Debug
fn afficher_debug<T: std::fmt::Display + std::fmt::Debug>(val: T) {
println!("Display: {val} Debug: {val:?}");
}
3.2 Clauses where
La clause where rend le code plus lisible quand les bounds sont nombreux.
// Inline — difficile à lire
fn comparer<T: PartialOrd + std::fmt::Display, U: std::fmt::Debug>(a: T, b: T, extra: U) -> bool {
println!("extra: {extra:?}");
a > b
}
// Avec where — beaucoup plus clair
fn comparer<T, U>(a: T, b: T, extra: U) -> bool
where
T: PartialOrd + std::fmt::Display,
U: std::fmt::Debug,
{
println!("extra: {extra:?}");
a > b
}
3.3 Plusieurs bounds
use std::fmt::{Display, Debug};
fn traiter<T>(valeur: T)
where
T: Display + Debug + Clone + PartialEq,
{
let copie = valeur.clone();
println!("Display: {valeur}");
println!("Debug: {valeur:?}");
println!("Égaux: {}", valeur == copie);
}
4. Implémentations conditionnelles
use std::fmt::Display;
struct Wrapper<T>(T);
// Méthode disponible pour tous les T
impl<T> Wrapper<T> {
fn valeur(&self) -> &T {
&self.0
}
}
// Méthode disponible uniquement si T: Display
impl<T: Display> Wrapper<T> {
fn afficher(&self) {
println!("{}", self.0);
}
}
// Implémenter un trait conditionnellement (blanket impl)
impl<T: Display> std::fmt::Debug for Wrapper<T> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Wrapper({})", self.0)
}
}
let w = Wrapper(42);
w.afficher(); // disponible car i32: Display
5. Generics dans les traits
Types associés vs paramètres génériques
// Type associé — une seule implémentation par type
trait Convertir {
type Sortie;
fn convertir(self) -> Self::Sortie;
}
impl Convertir for i32 {
type Sortie = f64;
fn convertir(self) -> f64 { self as f64 }
}
// Paramètre générique — plusieurs implémentations possibles
trait ConvertirEn<T> {
fn convertir_en(self) -> T;
}
impl ConvertirEn<f64> for i32 {
fn convertir_en(self) -> f64 { self as f64 }
}
impl ConvertirEn<String> for i32 {
fn convertir_en(self) -> String { self.to_string() }
}
`impl Trait` en paramètre et en retour
// En paramètre : syntaxe courte pour un trait bound
fn afficher(val: impl Display) {
println!("{val}");
}
// Équivalent à : fn afficher<T: Display>(val: T)
// En retour : type concret opaque
fn creer_iterateur() -> impl Iterator<Item = i32> {
vec![1, 2, 3].into_iter()
}
// Le type exact est caché, seul Iterator est exposé
6. Monomorphisation
Rust génère une version spécialisée du code pour chaque type concret utilisé.
fn identite<T>(x: T) -> T { x }
identite(5i32); // génère : fn identite_i32(x: i32) -> i32
identite(3.14f64); // génère : fn identite_f64(x: f64) -> f64
identite("texte"); // génère : fn identite_str(x: &str) -> &str
Conséquences :
- ✅ Zéro coût à l'exécution (pas d'appel indirect, pas de boxing)
- ✅ Le compilateur peut optimiser chaque version
- ⚠️ Binaire plus grand si beaucoup de types différents utilisés
Comparaison avec dyn Trait (dispatch dynamique) :
// Generics (statique) — résolu à la compilation
fn appeler_generique<T: Parler>(animal: &T) {
animal.parler();
}
// dyn Trait (dynamique) — résolu à l'exécution via vtable
fn appeler_dynamique(animal: &dyn Parler) {
animal.parler();
}
// dyn Trait utile pour des collections hétérogènes
let animaux: Vec<Box<dyn Parler>> = vec![
Box::new(Chien),
Box::new(Chat),
];
Règle : Préférez les génériques pour la performance. Utilisez
dyn Traitquand vous avez besoin de types hétérogènes à l'exécution.