
Fonctions et structures de contrôle
Apprenez à définir et appeler des fonctions en Rust, et à utiliser les structures de contrôle (if/else, loop, while, for) pour gérer le flux d'exécution de vos programmes.
Organiser la logique et diriger l'exécution : fonctions et contrôle de flux
Après avoir exploré comment stocker et manipuler des données avec les variables et les types de données, il est temps de se pencher sur la manière d'organiser et de contrôler la logique de nos programmes Rust. Ce chapitre est dédié à deux piliers de la programmation : les fonctions, qui nous permettent de structurer notre code en blocs réutilisables, et les structures de contrôle, qui nous donnent les moyens de diriger le flux d'exécution en fonction de conditions ou de répéter des opérations.
Nous commencerons par examiner comment définir et appeler des fonctions en Rust, en prêtant attention à la syntaxe pour les paramètres et les valeurs de retour. Vous découvrirez que Rust est un langage basé sur les expressions, ce qui influence la manière dont les fonctions retournent des valeurs de façon concise et élégante.
Ensuite, nous plongerons dans les structures de contrôle. Les conditions `if/else` vous permettront d'exécuter différents blocs de code en fonction de la véracité d'une expression booléenne. Puis, nous explorerons les différentes formes de boucles (`loop`, `while`, et `for`) qui vous donneront les outils nécessaires pour l'itération et la répétition de tâches. Maîtriser ces concepts est essentiel pour écrire des programmes non triviaux et bien structurés.
Définir et appeler des fonctions : paramètres et valeurs de retour
Les fonctions sont omniprésentes en Rust. Vous avez déjà utilisé la fonction `main`, qui est le point d'entrée de tout programme Rust exécutable, ainsi que la macro `println!`. Les fonctions permettent de nommer un bloc de code et de l'exécuter à la demande. Elles sont essentielles pour réduire la duplication, améliorer la lisibilité et organiser le code en modules logiques.
Pour définir une fonction en Rust, on utilise le mot-clé `fn`, suivi du nom de la fonction, d'une liste de paramètres entre parenthèses, et du type de la valeur de retour (après une flèche `->`). Les noms de fonctions en Rust utilisent conventionnellement la casse `snake_case` (mots en minuscules séparés par des underscores).
Voici la syntaxe générale et quelques exemples :
// Définition d'une fonction simple sans paramètres ni valeur de retour explicite
fn dire_bonjour() {
println!("Bonjour, Rust!");
}
// Définition d'une fonction avec des paramètres
// Les types des paramètres DOIVENT être déclarés.
fn saluer_personne(nom: &str) {
println!("Salut, {}!", nom);
}
// Définition d'une fonction avec des paramètres et une valeur de retour
// Le type de la valeur de retour est spécifié après `->`.
fn additionner(a: i32, b: i32) -> i32 {
// En Rust, la dernière expression d'une fonction est implicitement retournée
// si elle n'est pas suivie d'un point-virgule.
a + b
// Equivalent à: return a + b;
}
fn main() {
dire_bonjour(); // Appel de la fonction
saluer_personne("Alice");
saluer_personne("Bob");
let resultat = additionner(5, 7);
println!("5 + 7 = {}", resultat);
let autre_resultat = additionner_avec_return(10, 20);
println!("10 + 20 = {}", autre_resultat);
}
// On peut aussi utiliser le mot-clé `return` pour un retour explicite,
// souvent utilisé pour retourner tôt d'une fonction.
fn additionner_avec_return(x: i32, y: i32) -> i32 {
return x + y;
// Du code ici ne serait jamais exécuté
}Une caractéristique importante de Rust est qu'il s'agit d'un langage basé sur les expressions. Cela signifie que la plupart des constructions, y compris les blocs de code (`{}`) et les instructions `if`, peuvent évaluer à une valeur. Dans le contexte des fonctions, si la dernière ligne d'une fonction est une expression (sans point-virgule à la fin), cette expression devient la valeur de retour de la fonction. C'est une manière idiomatique et concise de retourner des valeurs, bien que l'utilisation du mot-clé `return` soit également possible et parfois préférable pour la clarté ou pour des retours anticipés.
Les paramètres de fonction requièrent des annotations de type explicites. Rust n'effectue pas d'inférence de type pour les signatures de fonction (sauf pour les durées de vie, un concept plus avancé). Cela garantit que les interfaces entre les différentes parties du code sont toujours claires et bien définies.
Conditions `if/else` et expressions : prendre des décisions
La structure de contrôle `if/else` permet à votre programme de prendre des décisions en exécutant différents blocs de code en fonction d'une condition booléenne. La syntaxe est similaire à celle de nombreux autres langages.
La condition d'un `if` doit toujours être une expression qui s'évalue à un `bool`. Si la condition est `true`, le bloc de code associé à `if` est exécuté. Sinon, si une clause `else` est présente, son bloc de code est exécuté.
fn main() {
let nombre = 7;
if nombre < 5 {
println!("La condition était vraie (nombre < 5)");
} else {
println!("La condition était fausse (nombre >= 5)");
}
let temperature = 25;
if temperature > 30 {
println!("Il fait chaud !");
} else if temperature < 10 {
println!("Il fait froid !");
} else {
println!("La température est agréable.");
}
}Contrairement à certains langages, la condition dans un `if` en Rust doit impérativement être un `bool`. Rust ne convertira pas automatiquement des types non booléens en booléens (par exemple, un nombre non nul n'est pas automatiquement `true`).
Comme Rust est un langage basé sur les expressions, une instruction `if` peut être utilisée dans une instruction `let` pour assigner une valeur à une variable. Dans ce cas, tous les blocs de l'instruction `if` (y compris le bloc `else` s'il est présent et nécessaire) doivent retourner des valeurs du même type. Le bloc `else` est obligatoire si l'expression `if` est utilisée pour assigner une valeur, afin de garantir que la variable est toujours initialisée.
Exemple d'utilisation d'un `if` dans une instruction `let` :
fn main() {
let condition = true;
let valeur = if condition {
5 // Cette expression est de type i32
} else {
10 // Cette expression doit aussi être de type i32
// "dix" // Erreur ! Les types des blocs if et else doivent correspondre
};
println!("La valeur est : {}", valeur);
let message = if valeur > 7 {
"Plus grand que 7"
} else if valeur < 3 {
"Plus petit que 3"
} else {
"Entre 3 et 7 (inclus)"
};
println!("Message : {}", message);
}Cette capacité à utiliser `if` comme une expression est très puissante et permet d'écrire du code concis et expressif.
Boucles : `loop`, `while`, et `for` pour l'itération
Les boucles sont utilisées pour exécuter un bloc de code plusieurs fois. Rust propose trois types de boucles : `loop`, `while`, et `for`.
La boucle `loop` exécute un bloc de code indéfiniment jusqu'à ce qu'elle soit explicitement arrêtée, généralement avec le mot-clé `break`. Une boucle `loop` peut également retourner une valeur après `break`.Exemple de `loop` :
fn main() {
let mut compteur = 0;
let resultat_boucle = loop {
compteur += 1;
println!("Compteur : {}", compteur);
if compteur == 5 {
break compteur * 2; // Arrête la boucle et retourne compteur * 2
}
};
println!("Le résultat de la boucle est : {}", resultat_boucle); // Affiche 10
}La boucle `while` exécute un bloc de code tant qu'une condition booléenne reste `true`. Si la condition devient `false` avant la première itération, le bloc n'est jamais exécuté.Exemple de `while` :
fn main() {
let mut nombre = 3;
while nombre != 0 {
println!("{} !", nombre);
nombre -= 1;
}
println!("DECOLLAGE !");
}La boucle `for` est la boucle la plus couramment utilisée et la plus idiomatique en Rust pour itérer sur les éléments d'une collection, comme un tableau ou une plage (range). Elle est plus sûre et plus concise que d'utiliser une boucle `while` avec un compteur manuel et une indexation.Exemple de `for` avec un tableau et une plage :
fn main() {
let a = [10, 20, 30, 40, 50];
// Itérer sur les éléments d'un tableau
for element in a.iter() { // .iter() retourne un itérateur sur les éléments
println!("La valeur est : {}", element);
}
// Alternative pour les types `Copy` (comme i32) : `for element in a { ... }`
// si vous voulez consommer le tableau, ou `for element in &a { ... }` pour emprunter.
// Itérer sur une plage (range)
// (1..4) crée une plage de 1 inclus à 4 exclus (1, 2, 3)
for nombre in 1..4 {
println!("{} !", nombre);
}
println!("FIN DE LA PLAGE SIMPLE.");
// Pour une plage inversée, utilisez .rev()
for nombre_rev in (1..4).rev() {
println!("{} ! (inversé)", nombre_rev);
}
println!("DECOLLAGE (avec for et range) !");
}La boucle `for` utilise le système d'itérateurs de Rust, qui est un concept puissant et flexible que nous explorerons plus en détail ultérieurement. Pour l'instant, retenez que `for` est le moyen privilégié d'itérer sur des collections de manière sûre et efficace.
Les mots-clés `break` et `continue` peuvent être utilisés dans toutes les boucles. `break` arrête immédiatement la boucle en cours, tandis que `continue` arrête l'itération actuelle et passe à la suivante. Rust permet également d'utiliser des étiquettes de boucle (`'label: loop { ... break 'label; }`) pour spécifier quelle boucle `break` ou `continue` doit affecter dans le cas de boucles imbriquées, bien que leur usage soit moins fréquent.