
Boucles : `loop`, `while`, et `for` pour l'itération
Explorez les trois types de boucles en Rust : `loop` pour les répétitions infinies, `while` pour les conditions, et `for` pour l'itération sur des collections. Gérez efficacement les tâches répétitives.
Répéter des actions en Rust : le trio de boucles `loop`, `while`, et `for`
La capacité d'exécuter un bloc de code de manière répétée est une fonctionnalité fondamentale de la plupart des langages de programmation. En Rust, cette répétition, ou itération, est gérée principalement par trois structures de boucle : `loop`, `while`, et `for`. Chacune de ces boucles a ses propres caractéristiques et cas d'usage, offrant aux développeurs la flexibilité nécessaire pour gérer divers scénarios de répétition.
Ce sous-chapitre vous guidera à travers ces trois types de boucles. Nous commencerons par `loop`, la forme la plus simple de boucle infinie, que vous pouvez contrôler avec des instructions `break`. Ensuite, nous examinerons la boucle `while`, qui continue de s'exécuter tant qu'une condition spécifique reste vraie. Enfin, nous nous plongerons dans la boucle `for`, la construction la plus couramment utilisée et la plus idiomatique en Rust pour itérer sur les éléments d'une collection ou d'une plage.
Vous apprendrez également à utiliser les mots-clés `break` et `continue` pour affiner le comportement de vos boucles, et nous mentionnerons brièvement les étiquettes de boucle pour des scénarios de contrôle plus complexes. Maîtriser ces outils d'itération est crucial pour écrire des programmes qui peuvent traiter des ensembles de données, gérer des événements en continu, ou simplement automatiser des tâches répétitives.
La boucle `loop` : répétition infinie et retours de valeur
La boucle `loop` est la forme la plus basique de boucle en Rust. Elle crée une boucle qui s'exécute indéfiniment jusqu'à ce que vous l'arrêtiez explicitement en utilisant le mot-clé `break`. Si vous n'incluez pas de `break`, la boucle tournera pour toujours (ou jusqu'à ce que le programme soit interrompu extérieurement).
Syntaxe de `loop` :
fn main() {
let mut compteur = 0;
println!("Début de la boucle loop...");
loop {
compteur += 1;
println!("Itération numéro : {}", compteur);
if compteur >= 5 {
println!("Compteur a atteint 5, sortie de la boucle.");
break; // Arrête la boucle
}
}
println!("Après la boucle loop.");
}Une caractéristique intéressante de la boucle `loop` est qu'elle peut être utilisée comme une expression pour retourner une valeur. Lorsque vous utilisez `break` avec une valeur, cette valeur devient le résultat de l'expression `loop`. Cela peut être utile pour des scénarios de type "réessayer jusqu'à succès".
Exemple de `loop` retournant une valeur :
fn main() {
let mut tentatives = 0;
let limite_tentatives = 3;
let resultat_operation = loop {
tentatives += 1;
println!("Tentative {}...", tentatives);
// Simuler une opération qui peut réussir ou échouer
if tentatives == limite_tentatives { // Simule un succès à la 3ème tentative
break "Opération réussie!"; // Retourne cette chaîne
} else if tentatives > limite_tentatives + 1 { // Sécurité pour éviter boucle infinie si logique erronée
break "Trop de tentatives, échec.";
}
// Simuler une pause ou une autre tentative
std::thread::sleep(std::time::Duration::from_millis(100));
};
println!("Résultat final : {}", resultat_operation);
}Si une boucle `loop` n'a pas de `break` qui retourne une valeur, son type est `!` (le "never type"), indiquant qu'elle ne retourne jamais normalement. Si elle a un `break value;`, son type est le type de `value`.
La boucle `while` : itération conditionnelle
La boucle `while` exécute un bloc de code de manière répétée tant qu'une condition booléenne spécifiée reste `true`. Avant chaque itération (y compris la première), la condition est évaluée. Si la condition est `false` initialement, le corps de la boucle n'est jamais exécuté.
Syntaxe de `while` :
fn main() {
let mut nombre = 5;
while nombre > 0 {
println!("Nombre actuel : {}", nombre);
nombre -= 1; // Important de modifier la condition pour éviter une boucle infinie
}
println!("Fin de la boucle while, nombre est maintenant {}.", nombre);
// Exemple avec une condition initialement fausse
let mut condition_fausse = false;
while condition_fausse {
println!("Ce message ne sera jamais affiché.");
// Pour éviter un avertissement de boucle infinie si on oublie de changer condition_fausse
condition_fausse = true;
}
println!("Après la boucle while avec condition fausse.");
}Les boucles `while` sont utiles lorsque le nombre d'itérations n'est pas connu à l'avance, mais dépend d'une condition qui change pendant l'exécution de la boucle. Il est crucial de s'assurer que la condition finira par devenir `false` pour éviter une boucle infinie, sauf si c'est intentionnel et géré par un `break` interne.
La boucle `for` : itération sur des collections et des plages
La boucle `for` est la méthode la plus courante et la plus idiomatique en Rust pour itérer sur les éléments d'une collection (comme un tableau, un vecteur, une chaîne de caractères) ou sur une séquence générée (comme une plage de nombres). Elle est généralement plus sûre et plus concise que d'utiliser une boucle `while` avec un index manuel.
La boucle `for` fonctionne avec tout type qui implémente le trait `Iterator`. Ce trait définit comment produire une séquence d'éléments. De nombreux types de la bibliothèque standard de Rust, y compris les tableaux et les plages, sont itérables.
Syntaxe et exemples de `for` :
fn main() {
// Itérer sur les éléments d'un tableau
let mon_tableau = [10, 20, 30, 40, 50];
println!("Itération sur mon_tableau:");
for element in mon_tableau.iter() { // .iter() crée un itérateur sur les références aux éléments
println!("- Valeur : {}", element);
}
// Pour des types `Copy` comme i32, on peut aussi écrire :
// for element in mon_tableau { ... } // Consommerait le tableau s'il n'était pas Copy
// ou, plus explicitement pour emprunter:
// for element in &mon_tableau { ... }
// Itérer sur une plage (range) de nombres
// (1..=5) crée une plage inclusive de 1 à 5 (1, 2, 3, 4, 5)
println!("\nItération sur une plage de 1 à 5 inclus:");
for nombre in 1..=5 {
println!("- Nombre : {}", nombre);
}
// (1..5) crée une plage exclusive du dernier élément (1, 2, 3, 4)
println!("\nItération sur une plage de 1 à 5 exclus:");
for nombre in 1..5 {
println!("- Nombre : {}", nombre);
}
// Itérer sur les caractères d'une chaîne (String ou &str)
let message = String::from("Rust!");
println!("\nItération sur les caractères de '{}':", message);
for caractere in message.chars() { // .chars() retourne un itérateur sur les char
println!("- Caractère : {}", caractere);
}
}L'utilisation de `.iter()` sur une collection comme un tableau retourne un itérateur qui produit des références immuables (`&T`) à chaque élément. Si vous avez besoin de modifier les éléments de la collection dans la boucle, vous pouvez utiliser `.iter_mut()`, qui retourne un itérateur sur des références mutables (`&mut T`), à condition que la collection elle-même soit mutable.
fn main() {
let mut nombres_mutables = [1, 2, 3, 4, 5];
println!("Avant modification : {:?}", nombres_mutables);
for item in nombres_mutables.iter_mut() {
*item *= 2; // Déréférence et modifie la valeur
}
println!("Après modification : {:?}", nombres_mutables);
}Contrôle avancé des boucles : `break`, `continue`, et étiquettes
Les mots-clés `break` et `continue` peuvent être utilisés à l'intérieur de n'importe quel type de boucle (`loop`, `while`, `for`) pour en altérer le comportement normal :
- `break` : Termine immédiatement l'exécution de la boucle la plus interne dans laquelle il se trouve. Si `break` est utilisé dans une boucle `loop` avec une valeur (par exemple, `break expression;`), cette valeur devient le résultat de l'expression `loop`.
- `continue` : Saute le reste de l'itération actuelle de la boucle la plus interne et passe directement au début de la prochaine itération.
Exemple avec `continue` et `break` dans une boucle `for` :
fn main() {
for i in 0..10 {
if i % 2 == 0 { // Si i est pair
continue; // Saute cette itération et passe à la suivante
}
if i > 7 { // Si i est plus grand que 7
break; // Sort complètement de la boucle
}
println!("Nombre impair traité : {}", i);
}
// Affichera:
// Nombre impair traité : 1
// Nombre impair traité : 3
// Nombre impair traité : 5
// Nombre impair traité : 7
}Rust permet également d'utiliser des étiquettes de boucle pour spécifier quelle boucle un `break` ou `continue` doit affecter, ce qui est utile dans le cas de boucles imbriquées. Une étiquette est un nom suivi d'un deux-points (par exemple, `'outer: loop { ... }`). Vous pouvez alors utiliser `break 'outer;` ou `continue 'outer;`.
Exemple avec une étiquette de boucle :
fn main() {
'boucle_externe: loop {
println!("Entrée dans la boucle externe.");
let mut i = 0;
loop { // Boucle interne sans étiquette explicite
println!(" Entrée dans la boucle interne, i = {}.", i);
if i == 1 {
println!(" Continue sur la boucle externe.");
continue 'boucle_externe; // Recommence la boucle externe
}
if i == 2 {
println!(" Break de la boucle interne.");
break; // Sort de la boucle interne seulement
}
if i == 3 {
println!(" Break de la boucle externe.");
break 'boucle_externe; // Sort des deux boucles
}
i += 1;
}
println!("Après la boucle interne. (ne devrait pas être atteint si i==1 ou i==3)");
}
println!("Après la boucle externe.");
}Bien que les étiquettes de boucle soient disponibles, leur utilisation est relativement rare. Souvent, si la logique devient si complexe qu'elle nécessite des `break` ou `continue` étiquetés, il peut être préférable de refactoriser le code en fonctions plus petites pour améliorer la clarté.
En maîtrisant `loop`, `while`, et `for`, ainsi que les commandes `break` et `continue`, vous disposez d'un ensemble complet d'outils pour gérer toutes sortes de tâches répétitives et de logiques d'itération en Rust, de manière sûre et expressive.