Generics en Rust : pourquoi et comment les utiliser

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Introduction : que sont les Generics et pourquoi en a-t-on besoin en Rust ?



Rust est un langage de programmation système réputé pour sa sécurité, ses performances et son système de types strict. L'un des outils clés permettant d'écrire du code flexible et réutilisable sont les Generics (types génériques).



Les Generics permettent de créer des fonctions, des structures, des énumérations et des méthodes qui peuvent fonctionner avec différents types de données sans avoir à dupliquer le code. Au lieu d'écrire une fonction séparée pour i32, f64 ou String, vous écrivez une seule version générique.



Par exemple, sans Generics, vous devriez créer trois fonctions différentes pour trier trois types différents. Avec les Generics, une seule suffit. Cela réduit non seulement la quantité de code, mais améliore aussi sa fiabilité : le compilateur vérifie la validité des types au moment de la compilation, et non lors de l'exécution.



Partie principale : comment fonctionnent les Generics en Rust



1. Fonctions génériques : écrivez une fois — utilisez pour n'importe quel type



La façon la plus simple de découvrir les Generics est à travers les fonctions génériques. Imaginez que vous devez écrire une fonction qui retourne la plus grande de deux valeurs. Au lieu de l'écrire séparément pour chaque type, utilisez la généricité :



fn largest<T: PartialOrd>(list: &[T]) -> &T {    let mut largest = &list[0];    for item in list {        if item > largest {            largest = item;        }    }    largest}

fn main() { let numbers = vec![34, 50, 25, 100, 65]; let result = largest(&numbers); println!("Le plus grand nombre : {}", result);

let chars = vec!['y', 'm', 'a', 'q']; let result = largest(&chars); println!("Le plus grand caractère : {}", result);}


Ici, <T: PartialOrd> signifie que la fonction fonctionne avec n'importe quel type T qui prend en charge la comparaison (le trait PartialOrd). C'est ce qu'on appelle une contrainte de trait — un concept important qui sera abordé plus loin.



2. Structures et énumérations génériques : flexibilité dans la définition des données



Les Generics ne s'appliquent pas seulement aux fonctions, mais aussi aux structures de données. Un exemple classique est la structure Point, qui peut stocker des coordonnées de différents types :



struct Point<T> {    x: T,    y: T,}

fn main() { let integer_point = Point { x: 5, y: 10 }; let float_point = Point { x: 1.0, y: 4.0 }; // let wont_work = Point { x: 5, y: 4.0 }; // Erreur ! Types différents}


Si vous avez besoin que x et y puissent être de types différents, utilisez plusieurs paramètres génériques :



struct Point<T, U> {    x: T,    y: U,}

fn main() { let mixed_point = Point { x: 5, y: 4.0 }; // Ça marche !}


La même chose fonctionne pour les énumérations. L'exemple le plus connu est Option<T> et Result<T, E>, qui sont génériques :



enum Option<T> {    Some(T),    None,}

enum Result<T, E> { Ok(T), Err(E),}


3. Méthodes dans les structures génériques : ajouter de la logique



Vous pouvez définir des méthodes pour des structures génériques en utilisant un bloc impl. Vous pouvez ainsi créer des méthodes qui ne fonctionnent que pour certains types :



struct Point<T> {    x: T,    y: T,}

// Méthodes pour tous les types Timpl<T> Point<T> { fn x(&self) -> &T { &self.x }}

// Méthode uniquement pour le type f64impl Point<f64> { fn distance_from_origin(&self) -> f64 { (self.x.powi(2) + self.y.powi(2)).sqrt() }}

fn main() { let p = Point { x: 3.0, y: 4.0 }; println!("p.x = {}", p.x()); println!("Distance par rapport à l'origine : {}", p.distance_

Blogs

Book Recommendations