Le type Option
Rust ne possède ni null ni undefined. L’absence de valeur est représentée par un type à part entière, Option<T>, que le compilateur oblige à traiter. Cette page recense les usages idiomatiques du type.
Choisir le bon outil
| Situation | Pattern | Exemple |
|---|---|---|
| Transformer la valeur si elle est présente | map | opt.map(|s| s.len()) |
Transformer en produisant un autre Option | and_then | opt.and_then(|s| s.parse().ok()) |
| Ne garder que si un prédicat est vérifié | filter | opt.filter(|s| !s.is_empty()) |
| Fournir une valeur par défaut | unwrap_or_else, unwrap_or_default | opt.unwrap_or_default() |
| Sortie anticipée puis code linéaire | let ... else | let Some(x) = opt else { return; }; |
| Propager l’absence à l’appelant | ? | let x = opt?; |
| L’absence est une erreur pour l’appelant | ok_or_else | opt.ok_or_else(|| Erreur::Absent)? |
| Un seul cas intéresse, avec effet de bord | if let | if let Some(x) = opt { ... } |
| Les deux branches font un vrai travail | match | voir plus bas |
| Test booléen | is_some_and, is_none_or | opt.is_some_and(|x| x > 3) |
Tip
Règle empirique : si un match comporte une branche None => None ou None => valeur_constante, c’est un combinateur déguisé. Il gagne à être réécrit.
Modèle mental
Option<T> n’est pas un T « nullable » : c’est une énumération à deux variantes, comme on pourrait en écrire soi-même.
Trois conséquences pratiques :
- Pas de propagation implicite. Là où d’autres langages proposent un chaînage optionnel (
a?.b?.c), Rust impose d’écrire explicitement la chaîne, avecand_thenou l’opérateur?. - Aucun surcoût dans les cas courants. Grâce à l’optimisation du pointeur nul,
Option<Box<T>>,Option<&T>ouOption<NonZeroU32>occupent exactement la même place que le type nu : le discriminant est encodé dans une valeur autrement impossible. Optionest annoté#[must_use]. Ignorer unOptionretourné déclenche un avertissement du compilateur.
Info
Une analogie utile : Option<T> se comporte comme un Vec<T> de capacité 1. Ce n’est pas qu’une image — Option implémente IntoIterator, et nombre de ses combinateurs portent le nom de leur équivalent sur Iterator, avec la même sémantique : map, filter, flatten, zip.
Option ou Result ?
Option<T> | Result<T, E> | |
|---|---|---|
| Sémantique | L’absence est normale et attendue | Quelque chose a échoué |
| L’appelant a besoin de savoir pourquoi | Non | Oui |
| Exemples | HashMap::get, Iterator::next, champ facultatif | Entrées/sorties, analyse syntaxique, validation |
S’il faut un commentaire pour expliquer ce que None signifie à cet endroit, c’est probablement un Result accompagné d’une énumération d’erreurs qui convient.
Extraire la valeur
let ... else
Stabilisé en Rust 1.65, c’est le pattern par défaut pour une sortie anticipée : il déstructure ou diverge, sans indenter la suite du code.
Le bloc else doit diverger — return, break, continue, panic! — et le compilateur le vérifie. Comparée à des match imbriqués, la version let ... else économise deux niveaux d’indentation et garde la logique métier à gauche.
if let
Lorsqu’un seul cas est intéressant :
Avec un else quand les deux branches sont courtes :
Chaînage de let (let chains)
Depuis Rust 1.88 et l’édition 2024, plusieurs conditions peuvent être combinées :
Avant cette version, ou hors édition 2024, il faut imbriquer les blocs ou passer par is_some_and.
while let
Pour consommer jusqu’à épuisement :
match
match reste justifié quand les deux branches effectuent un travail réel :
Note
Sur un &Option<T>, les variables liées deviennent automatiquement des références, sans avoir à écrire ref :
Ce sont les modes de liaison par défaut, détaillés dans Déstructuration et patterns. Dans ce cas précis, self.nom.as_ref().map_or(0, |n| n.len()) reste toutefois plus idiomatique.
Transformer : les combinateurs
C’est le cas majoritaire dans du code réel.
Les fondamentaux
Sortir de l’Option avec une valeur de repli
Warning
unwrap_or(String::new()) alloue systématiquement, y compris quand l’Option vaut Some. Il faut lui préférer unwrap_or_default() ou unwrap_or_else.
Tests booléens
is_some_and remplace avantageusement opt.map(|x| x > 3).unwrap_or(false).
Effet de bord dans une chaîne
Une chaîne réaliste
Extraction d’un champ textuel facultatif, non vide et débarrassé de ses espaces :
La version équivalente en match fait une douzaine de lignes et trois niveaux d’imbrication.
Tip
La lisibilité s’inverse au-delà de quatre maillons environ, ou dès que les closures contiennent une logique substantielle. Il faut alors couper en variables intermédiaires nommées, ou revenir à let ... else.
Références et propriété
C’est la principale source de friction. Un Option<T> possède son T : toute méthode qui prend self consomme l’Option.
La famille as_*
as_ref() est le réflexe pour lire sans consommer :
as_deref() donne le type emprunté naturel, ce qui est déterminant pour les signatures publiques :
La même logique s’applique à Option<Vec<T>> → Option<&[T]> et Option<PathBuf> → Option<&Path>. Le mécanisme sous-jacent est décrit dans Le mécanisme Deref.
Revenir d’une référence à une valeur
Extraire une valeur depuis un &mut self
Il est impossible de déplacer une valeur hors d’une référence mutable. take() résout ce cas en laissant None à la place :
Méthodes voisines :
Info
Option::take est le pendant de std::mem::take pour les champs facultatifs. C’est le pattern canonique des automates à états et des constructeurs de type fn build(&mut self) -> Chose.
Propager
L’opérateur ?
Dans une fonction qui retourne elle-même un Option, ? court-circuite sur None :
Warning
? ne mélange pas Option et Result au sein d’une même fonction : il faut convertir explicitement de l’un vers l’autre.
Les ponts entre Option et Result
Le cas typique dans un gestionnaire de requêtes :
transpose
transpose échange les deux couches :
Le besoin apparaît typiquement lorsqu’un champ facultatif doit être analysé : absent, le résultat est valide sans valeur ; présent mais invalide, c’est une erreur.
Interopérabilité avec les itérateurs
Option implémente IntoIterator et se comporte comme une séquence de zéro ou un élément, ce qui débloque plusieurs idiomes :
filter_map est la forme canonique, à préférer à .filter(Option::is_some).map(Option::unwrap).
Note
Côté Iterator, les méthodes next(), find(), max(), min() et last() retournent des Option : les combinateurs présentés ici s’enchaînent donc naturellement à leur suite.
Anti-patterns
is_some() suivi de unwrap()
Le compilateur ne relie pas les deux instructions : unwrap reste une source potentielle de panique. Clippy le signale sous le nom unnecessary_unwrap.
Un match là où un combinateur suffit
unwrap() en production
unwrap() n’apprend rien à celui qui lit la trace d’appels. Trois solutions selon le cas :
Le message d’expect doit expliquer pourquoi la valeur ne peut pas être None, et non ce qui a échoué. unwrap() nu se justifie dans les tests, les prototypes et les scripts.
clone() pour contourner l’emprunteur
opt.clone().unwrap() sur un Option<String> dans une boucle chaude, c’est une allocation par tour. Dans la grande majorité des cas, as_ref() ou as_deref() résout le problème sans copie. Le clone() doit rester un choix délibéré.
Option<Vec<T>> là où le vide suffit
Si None et Some(vec![]) ont la même signification pour les appelants, ce sont deux représentations d’un même état. Un Vec<T> simple suffit. L’exception légitime : quand « non renseigné » et « renseigné vide » sont sémantiquement distincts, par exemple dans un formulaire ou une modification partielle.
Option<Option<T>> sans raison
C’est presque toujours un flatten() oublié. Le cas légitime est la sémantique d’un PATCH JSON, où None signifie « champ absent », Some(None) « mettre à null » et Some(Some(v)) « affecter ». L’intention mérite alors d’être documentée.
Un booléen accompagnant une valeur
Les états invalides deviennent impossibles à représenter. C’est l’argument principal en faveur d’Option.
Conception d’API
En paramètre, Option<&T> est préférable à &Option<T> : un appelant qui n’a pas d’Option sous la main peut passer Some(&x) sans en construire une.
En retour, exposer Option<&T> avec le type emprunté naturel (&str, &[T], &Path) au moyen d’as_deref().
Dans une structure, un Option par champ réellement facultatif. Si trois champs valent None ou Some ensemble, il vaut mieux extraire une sous-structure placée dans un unique Option.
Avec serde :
Option combiné à #[serde(default)] rend le champ facultatif à la désérialisation, tandis que skip_serializing_if évite d’émettre des null.
Aide-mémoire
Ressources
std::option::Option— la documentation est courte et mérite une lecture intégrale- Clippy —
cargo clippydétecte la majorité des anti-patterns ci-dessus :unnecessary_unwrap,option_if_let_else,manual_map,redundant_clone - Rust API Guidelines — section consacrée aux paramètres facultatifs