
Définir et appeler des fonctions (paramètres et valeurs de retour)
Maîtrisez la création de fonctions en Rust : syntaxe, passage de paramètres typés, gestion des valeurs de retour et la puissance des expressions pour des retours implicites.
Les fonctions : blocs de construction réutilisables de votre code Rust
Au coeur de tout programme bien structuré se trouvent les fonctions. En Rust, comme dans de nombreux autres langages, les fonctions sont des blocs de code nommés qui accomplissent une tâche spécifique et peuvent être appelés (exécutés) à plusieurs reprises depuis différentes parties de votre programme. Elles sont fondamentales pour l'organisation du code, la réduction de la duplication, et l'amélioration de la lisibilité et de la maintenabilité.
Ce sous-chapitre se concentre sur les mécanismes de définition et d'appel de fonctions en Rust. Nous allons décortiquer la syntaxe pour déclarer une fonction, spécifier ses paramètres (les entrées qu'elle accepte), et définir le type de la valeur qu'elle retourne (sa sortie). Une attention particulière sera portée à la manière dont Rust, en tant que langage basé sur les expressions, gère les valeurs de retour de manière élégante.
Vous avez déjà rencontré la fonction `main`, point d'entrée de tout exécutable Rust, et probablement utilisé des macros comme `println!`. Il est maintenant temps d'apprendre à créer vos propres fonctions pour encapsuler la logique métier et construire des applications modulaires.
Syntaxe de définition d'une fonction en Rust
La définition d'une fonction en Rust commence par le mot-clé `fn` (abréviation de function). Il est suivi du nom de la fonction. Par convention, les noms de fonctions en Rust utilisent la casse `snake_case`, c'est-à-dire des mots en minuscules séparés par des underscores (`_`).
Après le nom de la fonction, viennent des parenthèses `()` qui peuvent contenir la liste des paramètres. Chaque paramètre doit avoir son type explicitement annoté. Enfin, après les parenthèses, vous pouvez spécifier le type de la valeur de retour de la fonction en utilisant une flèche `->` suivie du type. Si la fonction ne retourne rien d'explicite, le type de retour est `()` (le type unité), et cette partie (la flèche et le type) peut être omise.
Voici la structure générale :
fn nom_de_la_fonction(parametre1: Type1, parametre2: Type2) -> TypeDeRetour {
// Corps de la fonction : les instructions et expressions
// ...
valeur_a_retourner // Si c'est la dernière expression, elle est retournée implicitement
}Examinons quelques exemples concrets :
// Fonction sans paramètres et sans valeur de retour explicite (retourne implicitement ())
fn afficher_message_simple() {
println!("Ceci est un message simple.");
}
// Fonction avec un paramètre
fn feliciter(nom: &str, score: u32) {
println!("Félicitations, {} ! Votre score est de {}.", nom, score);
}
// Fonction avec deux paramètres et une valeur de retour
fn multiplier(x: i32, y: i32) -> i32 {
x * y // La valeur de cette expression (le produit de x et y) est retournée
}
// Fonction principale pour appeler les autres
fn main() {
afficher_message_simple();
feliciter("Jeanne", 1500);
let a = 5;
let b = 8;
let produit = multiplier(a, b);
println!("Le produit de {} et {} est {}.", a, b, produit);
let autre_produit = multiplier(10, -3);
println!("Un autre produit : {}.", autre_produit);
}Dans `multiplier`, l'expression `x * y` est la dernière du corps de la fonction et n'est pas suivie d'un point-virgule. En Rust, cela signifie que la valeur de cette expression est automatiquement retournée par la fonction. C'est une caractéristique idiomatique de Rust, liée à sa nature de langage basé sur les expressions.
Paramètres de fonction : spécifier les entrées
Les paramètres sont des variables spéciales qui font partie de la signature d'une fonction. Ils servent à passer des valeurs à la fonction lorsqu'elle est appelée, permettant ainsi à la fonction d'opérer sur des données spécifiques. Dans la définition d'une fonction, vous devez déclarer le nom et le type de chaque paramètre.
L'annotation de type pour chaque paramètre est obligatoire en Rust. Le compilateur n'essaiera pas d'inférer les types des paramètres de fonction (à l'exception des durées de vie, un concept plus avancé non pertinent ici).
fn afficher_coordonnees(x: f64, y: f64, nom_point: &str) {
println!("Point '{}' : (x={}, y={})", nom_point, x, y);
}
fn est_majeur(age: u8, limite_age: u8) -> bool {
age >= limite_age
}
fn main() {
afficher_coordonnees(10.5, -3.2, "Centre");
afficher_coordonnees(0.0, 0.0, "Origine");
let age_personne = 20;
let age_legal_fr = 18;
if est_majeur(age_personne, age_legal_fr) {
println!("La personne de {} ans est majeure (limite {} ans).", age_personne, age_legal_fr);
} else {
println!("La personne de {} ans n'est pas majeure (limite {} ans).", age_personne, age_legal_fr);
}
}Lorsque vous appelez une fonction, vous devez fournir des arguments pour chaque paramètre, dans le même ordre et avec des types compatibles. Rust vérifiera à la compilation que les types des arguments correspondent aux types des paramètres déclarés dans la signature de la fonction. Cette vérification stricte des types contribue grandement à la robustesse des programmes Rust en détectant les erreurs d'incompatibilité de type avant même l'exécution.
Valeurs de retour : comment les fonctions produisent des résultats
Les fonctions peuvent retourner des valeurs à leur appelant. Le type de la valeur de retour est spécifié après une flèche `->` dans la signature de la fonction. Si une fonction ne retourne aucune valeur significative, son type de retour est `()` (le type unité), et cette partie peut être omise dans la signature (elle est alors implicite).
En Rust, il y a deux manières principales de retourner une valeur d'une fonction :
- Retour implicite : Si la dernière ligne du corps de la fonction est une expression (c'est-à-dire qu'elle n'est pas terminée par un point-virgule), la valeur de cette expression est automatiquement retournée. C'est la manière la plus idiomatique en Rust.
- Retour explicite avec `return` : Vous pouvez utiliser le mot-clé `return` suivi d'une expression pour retourner une valeur à n'importe quel point de la fonction. Utiliser `return` termine immédiatement l'exécution de la fonction. C'est souvent utilisé pour des retours anticipés (par exemple, à l'intérieur d'une condition ou d'une boucle).
Illustrons cela avec des exemples :
// Retour implicite
fn carre(nombre: i32) -> i32 {
nombre * nombre // Pas de point-virgule, cette valeur est retournée
}
// Retour explicite avec `return`
fn trouver_pair_ou_erreur(nombres: &[i32]) -> Option { // Retourne un Option
for &n in nombres {
if n % 2 == 0 {
return Some(n); // Retour anticipé dès qu'un nombre pair est trouvé
}
}
None // Si aucune valeur paire n'est trouvée, retourne None (retour implicite de la dernière expression)
}
// Fonction ne retournant rien d'explicite (retourne implicitement ())
fn imprimer_separation() {
println!("--------------------");
// Pas de `-> ()` nécessaire, c'est implicite
}
fn main() {
let x = 5;
println!("Le carré de {} est {}.", x, carre(x));
imprimer_separation();
let liste1 = [1, 3, 5, 7];
let liste2 = [1, 3, 4, 6];
match trouver_pair_ou_erreur(&liste1) {
Some(val) => println!("Premier pair trouvé dans {:?}: {}", liste1, val),
None => println!("Aucun pair trouvé dans {:?}", liste1),
}
match trouver_pair_ou_erreur(&liste2) {
Some(val) => println!("Premier pair trouvé dans {:?}: {}", liste2, val),
None => println!("Aucun pair trouvé dans {:?}", liste2),
}
} La distinction entre une expression et une instruction est importante ici. Une expression évalue à une valeur (par exemple, `5 + 6`, `x * y`, `true`). Une instruction effectue une action et ne retourne pas de valeur (par exemple, une déclaration `let`, ou une expression terminée par un point-virgule comme `x * y;`). Terminer une expression par un point-virgule la transforme en instruction, et elle ne peut donc plus être utilisée comme valeur de retour implicite d'une fonction.
Par exemple :
fn mauvais_retour(a: i32, b: i32) -> i32 {
// a + b; // ERREUR : ceci est une instruction (type ()), mais la fonction attend i32
a + b // CORRECT : ceci est une expression, sa valeur est retournée
}Maîtriser cette nuance entre expression et instruction, ainsi que la convention du retour implicite, est clé pour écrire du code Rust concis et élégant. Les fonctions bien conçues, avec des paramètres clairement typés et des valeurs de retour explicites (ou implicites via des expressions), sont la fondation d'un code Rust robuste et maintenable.