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
```html

Introduction aux Generics en Rust

La programmation générique (generics) est l'un des concepts clés de Rust, permettant d'écrire un code flexible, réutilisable et sûr. Si vous passez à Rust depuis d'autres langages (par exemple, C++ ou Java), vous avez probablement déjà rencontré des templates ou des generics. En Rust, les generics fonctionnent de manière similaire, mais avec un accent particulier sur la performance et la sécurité mémoire.

Pourquoi avons-nous besoin de generics ? Imaginez que vous deviez écrire une fonction qui trouve l'élément maximum dans une liste. Sans generics, vous devriez écrire une version séparée pour i32, f64, String et chaque type personnalisé. Avec les generics, vous écrivez une seule fonction qui fonctionne avec n'importe quel type prenant en charge la comparaison.

Dans cet article, nous allons voir ce que sont les generics en Rust, comment les utiliser avec des fonctions, des structures et des traits, et nous examinerons des exemples pratiques.

Bases des Generics : fonctions et structures

Fonctions génériques

La façon la plus simple de découvrir les generics est d'écrire une fonction générique. La syntaxe utilise les chevrons <T>, où T est un paramètre de type (type parameter).

fn largest<T>(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);
}

Cependant, ce code ne compilera pas ! Rust exige que le type T implémente le trait PartialOrd (pour la comparaison). Nous verrons les contraintes (trait bounds) plus tard, mais retenez ceci : les generics permettent de s'abstraire du type concret, mais le compilateur doit savoir quelles opérations sont autorisées.

Structures génériques

Les generics fonctionnent non seulement avec les fonctions, mais aussi avec les types de données personnalisés. Par exemple, un point avec des coordonnées peut stocker des entiers ou des nombres à virgule flottante :

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 };
    println!("Point entier : ({}, {})", integer_point.x, integer_point.y);
    println!("Point flottant : ({}, {})", float_point.x, float_point.y);
}

Vous pouvez utiliser plusieurs paramètres de type si les champs ont des types différents :

struct Pair<T, U> {
    first: T,
    second: U,
}

fn main() {
    let pair = Pair { first: 42, second: "réponse" };
    println!("Paire : {} et {}", pair.first, pair.second);
}

Generics avec traits et contraintes (Trait Bounds)

Que sont les trait bounds ?

Les trait bounds sont un moyen d'indiquer au compilateur qu'un type générique doit implémenter certains traits. Sans eux, nous ne pouvons pas appeler de méthodes sur une valeur générique. Revenons à la fonction largest et corrigeons-la :

use std::cmp::PartialOrd;

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];
    println!("Le plus grand : {}", largest(&numbers));
}

Maintenant, la fonction n'accepte que les types qui prennent en charge la comparaison (PartialOrd).

Contraintes multiples

Vous pouvez spécifier plusieurs traits avec +. Par exemple, le type doit être à la fois comparable et clonable :

use std::fmt::Display;

fn print_and_compare
```                

Blogs

Book Recommendations