Generics en Rust : pourquoi en a-t-on besoin et comment les utiliser pour les débutants

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 ?

\n

Imaginez que vous deviez écrire une fonction qui trouve le plus grand élément dans une liste. Pour les entiers — une fonction, pour les nombres à virgule flottante — une autre, pour les chaînes de caractères — une troisième. Le code serait dupliqué et deviendrait difficile à maintenir. En Rust, ce problème est résolu par les Generics (programmation générique).

\n

Les Generics permettent d'écrire du code qui fonctionne avec différents types de données, sans perdre pour autant le typage strict. Au lieu de dupliquer les fonctions pour chaque type, vous écrivez une version générique unique. Lors de la compilation, Rust substitue le type concret et vérifie la correction. Cela offre :

\n
    \n
  • Réutilisabilité du code — une seule fonction pour tous les types.
  • \n
  • Sécurité — toutes les erreurs de type sont détectées lors de la compilation.
  • \n
  • Performance — grâce à la monomorphisation (le compilateur génère un code séparé pour chaque type utilisé), le code générique est aussi rapide que le code spécialisé.
  • \n
\n

Dans cet article, nous allons voir comment utiliser les Generics en Rust avec des exemples pratiques, en commençant par des fonctions simples et en terminant par des structures et des traits.

\n\n

1. Generics dans les fonctions : premier pas vers l'universalité

\n

Syntaxe et exemple de base

\n

Pour rendre une fonction générique, il faut spécifier un paramètre de type entre chevrons <T>. On utilise généralement des lettres majuscules : T, U, V. Voici à quoi ressemble une fonction qui retourne la valeur passée :

\n
fn identity<T>(value: T) -> T {\n    value\n}\n\nfn main() {\n    let num = identity(42);\n    let text = identity("Hello");\n    println!("{} {}", num, text); // Affiche : 42 Hello\n}
\n

Le compilateur déduit lui-même le type en fonction de l'argument passé. Vous pouvez aussi spécifier le type explicitement : identity::<i32>(42).

\n\n

Contraintes : les traits comme limites de types

\n

Que faire si nous voulons comparer des valeurs ? Écrire simplement value1 > value2 ne fonctionnera pas — tous les types ne supportent pas la comparaison. Il faut spécifier que le type doit implémenter le trait PartialOrd :

\n
fn largest<T: PartialOrd>(list: &[T]) -> &T {\n    let mut largest = &list[0];\n    for item in list {\n        if item > largest {\n            largest = item;\n        }\n    }\n    largest\n}\n\nfn main() {\n    let numbers = vec![34, 50, 25, 100, 65];\n    println!("Le plus grand nombre : {}", largest(&numbers));\n\n    let chars = vec!['y', 'm', 'a', 'q'];\n    println!("Le plus grand caractère : {}", largest(&chars));\n}
\n

Ici, T: PartialOrd est une contrainte de trait. Elle garantit que l'opérateur de comparaison est défini pour le type T. Sans cette contrainte, le code ne se compilerait pas.

\n\n

2. Generics dans les structures et les énumérations

\n

Structures avec champs génériques

\n

Les Generics fonctionnent également très bien avec les types personnalisés. Par exemple, la structure Point peut stocker des coordonnées de n'importe quel type :

\n
struct Point<T> {\n    x: T,\n    y: T,\n}\n\nfn main() {\n    let integer_point = Point { x: 5, y: 10 };\n    let float_point = Point { x: 1.0, y: 4.0 };\n    // let mixed = Point { x: 5, y: 4.0 }; // Erreur ! Les deux champs doivent être du même type\n}
\n

Si vous avez besoin que les champs puissent être de types différents, utilisez plusieurs paramètres :

\n
struct Point<T, U> {\n    x: T,\n    y: U,\n}\n\nfn main() {\n    let mixed = Point { x: 5, y: 4.0 }; // Maintenant ça fonctionne\n}
\n\n

Énumérations avec Generics

\n

L'énumération standard Option<T> est un excellent exemple d'énumération générique. Vous pouvez créer la vôtre :

\n
enum Result<T, E> {\n    Ok(T),\n    Err(E),\n}\n\n// Utilisation\nfn divide(a: f64, b: f64) -> Result<f64, String> {\n    if b                

Blogs

Book Recommendations