Fonctions génériques
Fait partie de la section Object Oriented Programming du Journey Rust de Coddy — leçon 27 sur 61.
Les génériques ne sont pas limités aux structs — vous pouvez également écrire des fonctions autonomes qui fonctionnent avec n'importe quel type. C'est utile lorsque vous avez besoin d'une fonction utilitaire qui n'appartient pas à une struct spécifique mais qui doit tout de même être flexible.
La syntaxe reflète ce que vous avez vu avec les structures. Déclarez le paramètre générique entre chevrons après le nom de la fonction, puis utilisez-le dans les paramètres et le type de retour :
fn identity<T>(value: T) -> T {
value
}
Cette fonction identity prend une valeur de n'importe quel type et la renvoie inchangée. Le <T> déclare le générique, value: T l'accepte comme paramètre, et -> T spécifie le type de retour. Lorsque vous appelez la fonction, Rust déduit le type concret :
let num = identity(42); // T est i32
let text = identity("hello"); // T est &str
Vous pouvez également utiliser plusieurs paramètres génériques dans les fonctions, tout comme avec les structures :
fn make_pair<T, U>(first: T, second: U) -> (T, U) {
(first, second)
}
let pair = make_pair(10, "ten"); // renvoie (i32, &str)
Les fonctions génériques vous permettent d'écrire une logique réutilisable une seule fois et de l'appliquer à de nombreux types, réduisant ainsi la duplication de code tout en maintenant la sécurité des types.
Défi
FacileConstruisons un module utilitaire avec des fonctions génériques qui peuvent fonctionner avec n'importe quel type ! Vous allez créer des fonctions autonomes qui démontrent comment les génériques rendent votre code flexible et réutilisable sans être lié à une structure spécifique.
Vous organiserez votre code sur deux fichiers :
utils.rs: Créez une collection de fonctions utilitaires génériques publiques :wrap_in_pair<T>— prend une seule valeur et renvoie un tuple contenant cette valeur deux fois :(value, value). Cela nécessite que le type soitClone, utilisez donc<T: Clone>swap<T, U>— prend deux valeurs de types potentiellement différents et les renvoie dans l'ordre inverse sous forme de tuple(U, T)
main.rs: Importez votre module utilitaire et démontrez ces fonctions génériques fonctionnant avec différents types. Montrez comment les mêmes définitions de fonctions gèrent les entiers, les flottants et les chaînes de caractères de manière transparente.
Dans votre fichier principal, démontrez vos fonctions utilitaires en :
- Utilisant
wrap_in_pairavec un entier (première entrée, analysée commei32) et en affichant les deux éléments - Utilisant
wrap_in_pairavec une chaîne de caractères (deuxième entrée) et en affichant les deux éléments - Utilisant
swapavec un entier (troisième entrée, analysée commei32) et une chaîne de caractères (quatrième entrée), puis en affichant le résultat inversé
Votre sortie doit suivre ce format :
Pair of ints: ({value}, {value})
Pair of strings: ({value}, {value})
Swapped: ({string}, {int})Par exemple, avec les entrées 5, hello, 42, et world :
Pair of ints: (5, 5)
Pair of strings: (hello, hello)
Swapped: (world, 42)Remarquez comment wrap_in_pair fonctionne de manière identique pour les entiers et les chaînes de caractères, et comment swap gère deux types complètement différents — c'est toute la flexibilité des fonctions génériques !
Vous recevrez quatre entrées : un entier, une chaîne de caractères, un autre entier et une autre chaîne de caractères.
Aide-mémoire
Les fonctions génériques vous permettent d'écrire des fonctions utilitaires autonomes qui fonctionnent avec n'importe quel type. Déclarez le paramètre générique entre chevrons après le nom de la fonction :
fn identity<T>(value: T) -> T {
value
}
Le <T> déclare le paramètre de type générique, qui peut ensuite être utilisé dans les paramètres de fonction et les types de retour. Rust déduit le type concret lorsque vous appelez la fonction :
let num = identity(42); // T est i32
let text = identity("hello"); // T est &str
Vous pouvez utiliser plusieurs paramètres génériques dans une seule fonction :
fn make_pair<T, U>(first: T, second: U) -> (T, U) {
(first, second)
}
let pair = make_pair(10, "ten"); // retourne (i32, &str)
Lorsqu'un type générique a besoin de capacités spécifiques, utilisez des bornes de trait (trait bounds) :
fn duplicate<T: Clone>(value: T) -> (T, T) {
(value.clone(), value)
}
Essayez vous-même
mod utils;
use std::io;
fn main() {
// Lire les quatre entrées
let mut input1 = String::new();
io::stdin().read_line(&mut input1).expect("Failed to read line");
let num1: i32 = input1.trim().parse().expect("Invalid integer");
let mut input2 = String::new();
io::stdin().read_line(&mut input2).expect("Failed to read line");
let str1 = input2.trim().to_string();
let mut input3 = String::new();
io::stdin().read_line(&mut input3).expect("Failed to read line");
let num2: i32 = input3.trim().parse().expect("Invalid integer");
let mut input4 = String::new();
io::stdin().read_line(&mut input4).expect("Failed to read line");
let str2 = input4.trim().to_string();
// TODO: Utiliser wrap_in_pair avec num1 et afficher le résultat
// Format : Pair of ints: ({value}, {value})
// TODO: Utiliser wrap_in_pair avec str1 et afficher le résultat
// Format : Pair of strings: ({value}, {value})
// TODO: Utiliser swap avec num2 et str2, puis afficher le résultat inversé
// Format : Swapped: ({string}, {int})
}
Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Object Oriented Programming
1Méthodes et comportement
Introduction aux blocs d'implémentationLe paramètre SelfMéthodes mutablesFonctions associéesBlocs d'implémentation multiplesChaînage de méthodesRécapitulatif - Actions sur le rectangle4Projet : Animal virtuel
Définir l'animalNourrir l'animal7Traits standards
Le trait DebugLe trait DisplayClone et CopyLes traits d'égalitéRécapitulatif - Point affichable10Projet : Système de documents
Le trait DrawComposant texte2Encapsulation et modules
Bases des modulesLe mot-clé publicChamps privésGettersSettersRécapitulatif - Casier sécurisé5Généricité
Structures génériquesMéthodes génériquesPlusieurs types génériquesFonctions génériquesRécapitulatif - Point de coordonnées8Les Traits comme contraintes
Syntaxe des contraintes de traitContraintes multiplesLa clause whereRetourner des types avec des traitsRécapitulatif - Afficheur générique11Design Patterns en Rust
Le pattern NewtypeCompositionLe trait DropFrom et IntoRécapitulatif - Mock de Smart Pointer3Enums avancées
Enums avec donnéesMéthodes sur les EnumsMatching des variantes de donnéesL'Enum Option revisitéeRécapitulatif - Enum Shape6Définition des Traits
Qu'est-ce qu'un Trait ?Implémenter des TraitsImplémentations par défautRedéfinir les comportements par défautTraits avec paramètresRécapitulatif - Lecteur multimédia