Contrôle de flux et expressions
Rust est un langage orienté expression : la plupart de ses constructions de contrôle produisent une valeur au lieu de se contenter de piloter l’exécution. Comprendre ce principe, et le type ! qui le rend cohérent, explique une bonne partie de la syntaxe idiomatique.
Presque tout est une expression
Rust distingue :
- les instructions (statements), qui ne produisent pas de valeur et se terminent par un point-virgule ;
- les expressions, qui en produisent une.
Un bloc { } est lui-même une expression : sa valeur est celle de sa dernière expression, écrite sans point-virgule.
Ajouter un point-virgule change la valeur du bloc en (), le tuple vide — c’est la cause la plus fréquente de l’erreur expected i32, found ().
Le corollaire pratique : if, match et loop s’utilisent partout où une valeur est attendue.
Note
C’est pourquoi Rust n’a pas d’opérateur ternaire : if en expression le remplace exactement, sans syntaxe supplémentaire.
Les branches doivent alors toutes produire le même type. if sans else vaut (), et ne peut donc être affecté qu’à une variable de type ().
Les conditions
La condition doit obligatoirement être un bool. Contrairement au C, if nombre { ... } ne compile pas : il n’existe pas de conversion implicite vers un booléen.
Pour le filtrage par motif — match, if let, while let, let ... else — voir Déstructuration et patterns, qui traite le sujet en détail.
Les boucles
Rust propose trois formes, sans équivalent du do...while :
for est de loin la forme la plus courante. Elle repose entièrement sur le trait IntoIterator, comme détaillé dans Les itérateurs.
break avec valeur
Seul loop peut renvoyer une valeur, en la passant à break :
C’est la forme idiomatique du « répéter jusqu’à obtenir quelque chose » : une tentative de connexion, un tirage jusqu’à satisfaction d’un critère, la lecture jusqu’à une sentinelle.
Les étiquettes
Une étiquette, préfixée d’une apostrophe, permet de cibler un break ou un continue sur une boucle englobante :
Les blocs étiquetés
Depuis Rust 1.65, un bloc peut lui aussi porter une étiquette et recevoir un break. Il ne boucle pas : il offre une sortie anticipée vers un point précis, avec une valeur.
Tip
C’est le remplaçant direct des drapeaux booléens et des variables resultat initialisées à None que l’on trouve dans le code venu d’autres langages. Contrairement à return, la sortie reste locale : la fonction poursuit son exécution après le bloc.
Le type ! et la divergence
Certaines expressions ne renvoient jamais de valeur, parce qu’elles interrompent le flot : return, break, continue, panic!, std::process::exit, ou une boucle infinie sans break. Leur type est !, appelé never type ou type divergent.
La propriété essentielle de ! est qu’il s’unifie avec n’importe quel autre type. Une expression divergente est donc acceptée dans une branche censée produire un i32, un String ou n’importe quoi d’autre :
C’est ce mécanisme, et non une règle spéciale, qui explique plusieurs constructions déjà rencontrées :
- le bloc
elsed’unlet ... elsedoit diverger, puisqu’il n’a aucune valeur à fournir à la liaison ; - un bras de
matchpeut appelerpanic!sans rompre la cohérence de type ; - l’opérateur
?peut interrompre la fonction au milieu d’une expression.
Une fonction qui ne rend jamais la main se déclare avec -> ! :
Les macros de divergence
| Macro | Usage |
|---|---|
panic!("message") | Erreur irrécupérable, avec message |
unreachable!() | Branche que la logique rend impossible à atteindre |
todo!() | Code non encore écrit — compile, échoue à l’exécution |
unimplemented!() | Cas volontairement non pris en charge |
Tip
todo!() est particulièrement utile pendant la conception : il permet d’écrire les signatures et de laisser le compilateur vérifier l’architecture avant d’implémenter le corps des fonctions. Les types sont validés, et chaque appel non implémenté échoue bruyamment s’il est atteint.
La sortie anticipée
L’opérateur ?
? propage l’échec à l’appelant et poursuit le flot normal sinon. Il fonctionne sur Result comme sur Option, mais sans mélange des deux au sein d’une même fonction.
let ... else
Pour déstructurer ou sortir, sans indenter la suite :
Les clauses de garde
L’idiome consiste à traiter les cas d’exclusion en tête de fonction, pour garder le cas nominal à plat plutôt que de l’enfouir sous plusieurs niveaux d’indentation.
ControlFlow et les itérateurs
Une chaîne d’itérateurs ne peut pas contenir de break. Pour interrompre un parcours en cours de route, la bibliothèque standard fournit l’énumération ControlFlow, associée à try_fold et try_for_each.
try_fold accepte en réalité tout type implémentant Try, ce qui inclut Option et Result — le parcours s’arrête alors au premier None ou Err :
Note
Avant d’atteindre try_fold, il faut vérifier qu’un consommateur dédié ne fait pas déjà le travail : find, position, any et all s’arrêtent tous dès que le résultat est connu. any et all sont notamment des boucles à sortie anticipée déguisées.
Ce qui n’existe pas
| Absent | Remplacé par |
|---|---|
Opérateur ternaire ? : | if en expression |
Chute d’un case à l’autre (fallthrough) | Les alternatives de motifs : 1 | 2 | 3 => ... |
goto | Étiquettes de boucle et blocs étiquetés |
do ... while | loop avec un break conditionnel en fin de corps |
Exceptions, try / catch | Result, ?, et panic! pour l’irrécupérable |
| Valeurs implicitement booléennes | Une condition doit être un bool |
Warning
Rust ne garantit pas l’optimisation des appels terminaux (tail call optimization). Une récursion profonde peut donc provoquer un débordement de pile, même écrite en position terminale. Pour un parcours de profondeur inconnue, mieux vaut une boucle avec une pile explicite.
À retenir
- Un bloc vaut sa dernière expression ; un point-virgule le fait valoir
(). if,matchetloopproduisent des valeurs — d’où l’absence de ternaire.- Seul
looppeut renvoyer une valeur viabreak; les blocs étiquetés étendent ce mécanisme hors des boucles. - Le type
!s’unifie avec tout : c’est ce qui autorisepanic!,returnou?au milieu d’une expression typée. - Dans une chaîne d’itérateurs, la sortie anticipée passe par
find,any,alloutry_fold.
Ressources
- Control Flow — chapitre du Rust Book
- Le type
never— documentation de! ControlFlow— l’énumération et ses usages- Expressions — la référence, qui détaille ce qui est expression et ce qui ne l’est pas