Les génériques en Rust : principes fondamentaux et applications typiques

Chapitre 1 : Notions de base

Section 1 : Sans l'utilisation des génériques

Lorsque l'on définit des fonctions avec une logique identique pour différents types, la'bsence de génériques entraîne une duplication de code excessive. Par exemple :

fn main() {
    let val_i8: i8 = obtenir_i8(5i8);
    let val_i16: i16 = obtenir_i16(5i16);
    let val_i32: i32 = obtenir_i32(5i32);
    let val_i64: i64 = obtenir_i64(5i64);

    let val_u8: u8 = obtenir_u8(5u8);
    let val_u16: u16 = obtenir_u16(5u16);
    let val_u32: u32 = obtenir_u32(5u32);
    let val_u64: u64 = obtenir_u64(5u64);
}

fn obtenir_i8(val: i8) -> i8 { val }
fn obtenir_i16(val: i16) -> i16 { val }
fn obtenir_i32(val: i32) -> i32 { val }
fn obtenir_i64(val: i64) -> i64 { val }
fn obtenir_u8(val: u8) -> u8 { val }
fn obtenir_u16(val: u16) -> u16 { val }
fn obtenir_u32(val: u32) -> u32 { val }
fn obtenir_u64(val: u64) -> u64 { val }

Section 2 : Introduction aux génériques

Les génériques permettent de simplifier le code en créant des fonctions génériques qui fonctionnnent avec n'importe quel type :

fn main() {
    let res_i8: i8 = extraire_valeur(5i8);
    let res_i16: i16 = extraire_valeur(5i16);
    let res_i32: i32 = extraire_valeur(5i32);
    let res_i64: i64 = extraire_valeur(5i64);

    let res_u8: u8 = extraire_valeur(5u8);
    let res_u16: u16 = extraire_valeur(5u16);
    let res_u32: u32 = extraire_valeur(5u32);
    let res_u64: u64 = extraire_valeur(5u64);
}

fn extraire_valeur<T>(donnee: T) -> T { donnee }

Section 3 : Avantages et mécanisme

Les génériques sont des abstractions qui remplacent des types concrets, réduisant ainsi la répétition dans le code. En Rust, ils n'entraînent pas de perte de performance car la monomorphisation est appliquée lors de la compilation : le compilateur substitue les génériques par des types spécifiques, générant du code optimisé.

Chapitre 2 : Utilisations courantes

Section 1 : Génériques dans les structures

Les structures peuvent utiliser des génériques pour stocker des données de types variés :

#[derive(Debug)]
struct Coordonnee<T> {
    abscisse: T,
    ordonnee: T,
}

fn main() {
    let point_entier: Coordonnee<i32> = Coordonnee { abscisse: 10, ordonnee: 20 };
    println!("{:?}", point_entier);

    let point_flottant: Coordonnee<f64> = Coordonnee { abscisse: 1.5, ordonnee: 2.8 };
    println!("{:?}", point_flottant);
}

Pour des types différents, on utilise plusieurs paramètres de type :

#[derive(Debug)]
struct Paquet<X, Y> {
    premier: X,
    second: Y,
}

fn main() {
    let mixte: Paquet<u32, &str> = Paquet { premier: 42, second: "test" };
    println!("{:?}", mixte);
}

Section 2 : Génériques dans les énumérations

Les énumérations standard comme Option et Result utilisent des génériques. Voici un exemple personnalisé :

#[derive(Debug)]
enum Notification<A, B> {
    Texte(u32),
    Donnee(A),
    Reference(B),
}

fn main() {
    let alerte1: Notification<i64, String> = Notification::Texte(1);
    let alerte2: Notification<i64, String> = Notification::Donnee(100);
    let alerte3: Notification<i64, String> = Notification::Reference("info".to_string());
    println!("{:?}", alerte1);
    println!("{:?}", alerte2);
    println!("{:?}", alerte3);
}

Section 3 : Génériques dans les méthodes

Les méthodes implémentées sur des structures génériques peuvent aussi utiliser des types génériques :

#[derive(Debug)]
struct Vecteur<T> {
    composante_x: T,
    composante_y: T,
}

impl<T> Vecteur<T> {
    fn obtenir_x(&self) -> &T {
        &self.composante_x
    }
}

fn main() {
    let v: Vecteur<i32> = Vecteur { composante_x: 5, composante_y: 10 };
    println!("x = {}", v.obtenir_x());
}

Les méthodes peuvent également introduire de nouveaux paramètres de type pour des opérations compelxes :

#[derive(Debug)]
struct Paire<U, V> {
    alpha: U,
    beta: V,
}

impl<U, V> Paire<U, V> {
    fn fusionner<W, X>(self, autre: Paire<W, X>) -> Paire<U, X> {
        Paire { alpha: self.alpha, beta: autre.beta }
    }
}

fn main() {
    let paire1: Paire<i32, f64> = Paire { alpha: 1, beta: 2.5 };
    let paire2: Paire<&str, char> = Paire { alpha: "rust", beta: 'R' };
    let resultat: Paire<i32, char> = paire1.fusionner(paire2);
    println!("alpha = {}, beta = {}", resultat.alpha, resultat.beta);
}

Étiquettes: Rust génériques monomorphisation structs enums

Publié le 21 juin à 03h59