Erreur Rust E0004 : non-exhaustive patterns not covered (match incomplet)
E0004 veut dire que votre match a oublié au moins une valeur possible, et que Rust refuse de deviner ce qui doit se passer dans ce cas. C’est l’une des erreurs les plus sympathiques de Rust : le compilateur vous dit exactement quel cas manque, et il écrit même la ligne pour vous. Cette page vous montre d’abord la solution, puis vous explique pourquoi cette vérification est l’un des meilleurs atouts de Rust. Pour les autres erreurs de cette série, consultez les erreurs du compilateur Rust.
Le message d’erreur
Section intitulée « Le message d’erreur »error[E0004]: non-exhaustive patterns: `OrderStatus::Delivered` not covered --> src/main.rs:8:11 | 8 | match status { | ^^^^^^ pattern `OrderStatus::Delivered` not covered |note: `OrderStatus` defined here --> src/main.rs:1:6 | 1 | enum OrderStatus { | ^^^^^^^^^^^... 4 | Delivered, // added later | --------- not covered = note: the matched value is of type `OrderStatus`help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown |10 ~ OrderStatus::Shipped => "on its way",11 ~ OrderStatus::Delivered => todo!(), |Le compilateur vous dit : « motifs non exhaustifs : OrderStatus::Delivered n’est pas couvert ». Il vous propose aussi d’ajouter une branche, soit avec un motif joker (_), soit avec le motif exact qu’il affiche.
Voici le programme qui la provoque :
enum OrderStatus { Pending, Shipped, Delivered, // added later}
fn describe(status: OrderStatus) -> &'static str { match status { OrderStatus::Pending => "waiting to be packed", OrderStatus::Shipped => "on its way", // ❌ nothing for Delivered }}
fn main() { println!("{}", describe(OrderStatus::Delivered));}(Les commentaires disent : Delivered a été ajoutée plus tard ; rien n’est prévu pour Delivered.)
Le texte entre les backticks change selon votre code. Vous verrez peut-être `None` not covered, `&_` not covered, `0_u8` and `4_u8..=u8::MAX` not covered ou `(true, false)` and `(false, true)` not covered. C’est la même erreur à chaque fois : Rust liste les valeurs pour lesquelles votre match n’a aucune branche.
Ce que ça veut dire
Section intitulée « Ce que ça veut dire »Lisez le message de haut en bas :
- Ligne 8 : les
^^^^^^désignent la valeur sur laquelle vous faites lematch,status. - « pattern
OrderStatus::Deliverednot covered » : c’est le cas que vous avez oublié. - La note saute à la définition de l’enum et marque la variante oubliée, à la ligne 4.
- L’aide montre la branche exacte à ajouter.
todo!()est un bouche-trou ; vous le remplacez par du vrai code.
Imaginez un distributeur avec trois boutons, dont seulement deux sont branchés. Appuyer sur le troisième ferait… quoi ? Rust ne vous laisse pas livrer une machine avec un bouton mort. Chaque bouton doit avoir une réponse, même si cette réponse est « hors service ».
Pourquoi Rust fait ça
Section intitulée « Pourquoi Rust fait ça »En Rust, un match doit être exhaustif : pour chaque valeur que le type peut contenir, une branche doit s’appliquer. S’il manquait une branche, le programme arriverait à un endroit sans aucun code à exécuter. D’autres langages passent discrètement au cas suivant, renvoient une valeur par défaut, ou plantent à l’exécution. Rust l’attrape avant même que le programme ne tourne. La leçon match couvre les bases.
Le vrai bénéfice arrive plus tard, quand votre code grandit. Supposons que vous ajoutiez une nouvelle variante à un enum utilisé dans dix match à travers votre projet :
enum OrderStatus { Pending, Shipped, Delivered, Returned, // nouveau !}Le compilateur vous donne maintenant un E0004 pour chaque match à mettre à jour :
error[E0004]: non-exhaustive patterns: `&OrderStatus::Returned` not covered --> src/main.rs:9:11error[E0004]: non-exhaustive patterns: `&OrderStatus::Returned` not covered --> src/main.rs:17:11C’est une liste de tâches écrite pour vous. Pas besoin de fouiller tout le code en espérant n’avoir oublié aucun endroit. Voilà pourquoi les développeurs Rust expérimentés aiment autant les enums : vous pouvez les modifier sans crainte.
Comment corriger
Section intitulée « Comment corriger »Solution 1 : ajouter la branche manquante
Section intitulée « Solution 1 : ajouter la branche manquante »Choisissez-la pour vos propres enums. C’est la bonne solution la plupart du temps, et c’est exactement ce que suggère la ligne help: de rustc. Ajoutez la branche, et remplacez todo!() par ce qui doit vraiment se passer :
enum OrderStatus { Pending, Shipped, Delivered,}
fn describe(status: OrderStatus) -> &'static str { match status { OrderStatus::Pending => "en attente d'emballage", OrderStatus::Shipped => "en route", OrderStatus::Delivered => "devant votre porte", // ✅ tous les cas sont traités }}
fn main() { println!("{}", describe(OrderStatus::Pending)); println!("{}", describe(OrderStatus::Delivered));}Sortie :
en attente d'emballagedevant votre porteSi plusieurs variantes doivent faire la même chose, réunissez-les avec | dans une seule branche : OrderStatus::Shipped | OrderStatus::Delivered => false. Vous nommez toujours chaque variante, donc le compilateur peut toujours vous prévenir pour les nouvelles. C’est pareil pour Option : si l’erreur dit `None` not covered, ajoutez une branche None => .... Voir Option.
Solution 2 : utiliser des plages et _ pour les nombres et le texte
Section intitulée « Solution 2 : utiliser des plages et _ pour les nombres et le texte »Choisissez-la quand vous faites un match sur des entiers, des char ou des chaînes. Un u8 a 256 valeurs possibles et un &str en a une infinité : impossible de toutes les lister. Utilisez des plages comme 1..=9 pour des groupes de nombres, et terminez par _ (ou par un nom comme other) pour « tout le reste » :
fn grade(score: u8) -> char { match score { 90..=100 => 'A', 75..=89 => 'B', 50..=74 => 'C', 0..=49 => 'F', _ => '?', // de 101 à 255 : pas une vraie note }}
fn run(command: &str) { match command { "start" => println!("Démarrage du minuteur"), "stop" => println!("Arrêt du minuteur"), other => println!("Commande inconnue : {other}"), }}
fn main() { println!("{} {} {}", grade(95), grade(62), grade(150)); run("start"); run("pause");}Sortie :
A C ?Démarrage du minuteurCommande inconnue : pausePour les chaînes, rustc le dit même directement : `&str` cannot be matched exhaustively, so a wildcard `_` is necessary (un &str ne peut pas être couvert de façon exhaustive, un joker _ est donc nécessaire). Pour les nombres, Rust vérifie vos plages avec précision. Si elles couvrent vraiment toutes les valeurs (par exemple de 0..=49 jusqu’à 90..=u8::MAX), vous n’avez pas du tout besoin de _. Pour en savoir plus : Correspondre à une plage.
Solution 3 : utiliser if let ou let else quand un seul cas vous intéresse
Section intitulée « Solution 3 : utiliser if let ou let else quand un seul cas vous intéresse »Choisissez-la quand vous voulez agir sur un seul motif et ignorer les autres. Un match complet avec une branche vide « ne rien faire » fonctionne, mais if let l’exprime plus clairement. let else sert à « sortir la valeur, ou quitter la fonction tout de suite » :
fn greet(nickname: Option<&str>) { if let Some(name) = nickname { println!("Salut, {name} !"); } // pas de surnom : on ne fait rien}
fn first_word_len(text: &str) -> usize { let Some(word) = text.split_whitespace().next() else { return 0; // texte vide : on sort tout de suite }; word.len()}
fn main() { greet(Some("Ferris")); greet(None); println!("{}", first_word_len("hello world")); println!("{}", first_word_len(" "));}Sortie :
Salut, Ferris !50(Le texte "hello world" reste en anglais, car len() compte des octets : un mot accentué changerait le résultat.)
Le bloc else d’un let else doit quitter le bloc courant (return, break, continue ou un panic). Voir if let et let-else.
Solution 4 : un fourre-tout _ => pour le reste (avec un inconvénient)
Section intitulée « Solution 4 : un fourre-tout _ => pour le reste (avec un inconvénient) »Choisissez-la quand la plupart des variantes d’un enum font vraiment la même chose. _ correspond à tout ce que les branches précédentes n’ont pas attrapé :
enum Key { Up, Down, Left, Right, Enter, Escape,}
fn handle(key: Key) { match key { Key::Enter => println!("Valider"), Key::Escape => println!("Annuler"), _ => println!("Déplacer le curseur"), }}
fn main() { handle(Key::Enter); handle(Key::Left); handle(Key::Escape);}Sortie :
ValiderDéplacer le curseurAnnulerSoyons honnêtes sur le prix à payer : _ désactive la vérification décrite dans Pourquoi Rust fait ça. Si vous ajoutez plus tard Key::Delete, le compilateur ne dit rien, et la touche Suppr déplace le curseur. C’est un bug que Rust aurait pu attraper pour vous. Pour vos propres enums, préférez lister les variantes avec | (Key::Up | Key::Down | Key::Left | Key::Right => ...). Gardez _ pour les nombres, les chaînes et les enums venant d’autres crates.
Où vous le verrez souvent
Section intitulée « Où vous le verrez souvent »Une Option sans None. match nickname { Some(name) => ... } donne `None` not covered. Correction : ajoutez une branche None =>, ou utilisez if let Some(name) = nickname s’il n’y a rien à faire pour None.
Les tuples et les booléens. Faire un match sur deux bool à la fois donne quatre combinaisons, et il est facile d’en oublier :
match (logged_in, is_admin) { (true, true) => println!("Bienvenue, admin"), (false, false) => println!("Veuillez vous connecter"),} // ❌ patterns `(true, false)` and `(false, true)` not covered(Le commentaire reprend le message du compilateur : les motifs (true, false) et (false, true) ne sont pas couverts.) Correction : ajoutez les branches manquantes, ou utilisez _ à l’intérieur du tuple, comme (false, _) => println!("Veuillez vous connecter"), qui couvre les deux cas « non connecté ». Voir Plusieurs valeurs dans une même branche.
Un motif dans un let. Écrire let Some(score) = saved;, c’est faire un let qui pourrait ne pas correspondre. C’est une erreur sœur, E0005, avec la même idée :
error[E0005]: refutable pattern in local binding --> src/main.rs:3:9 |3 | let Some(score) = saved; | ^^^^^^^^^^^ pattern `None` not covered | = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one varianthelp: you might want to use `let...else` to handle the variant that isn't matched |3 | let Some(score) = saved else { todo!() }; | ++++++++++++++++Le compilateur explique qu’un let exige un motif « irréfutable » (qui correspond toujours), et propose let...else pour traiter le cas qui ne correspond pas. Correction : let Some(score) = saved else { return; };, ou if let, comme dans la solution 3. Voir Les motifs dans let.
Les enums d’autres crates. Certains enums, comme std::io::ErrorKind, sont marqués #[non_exhaustive]. Leurs auteurs pourront ajouter des variantes plus tard : même si vous listez toutes les variantes visibles aujourd’hui, rustc dit `_` not covered. Correction : ajoutez une branche _ =>. Ici, elle est obligatoire, ce n’est pas un raccourci.
Erreurs liées
Section intitulée « Erreurs liées »- E0005 (refutable pattern in local binding, motif réfutable dans une liaison locale) : la même vérification « not covered », pour
letau lieu dematch. Montrée plus haut. - E0308 : types incompatibles, par exemple quand les branches d’un
matchrenvoient des types différents ("texte"dans une branche, un nombre dans une autre). - E0382 : borrow of partially moved value (emprunt d’une valeur partiellement déplacée), quand une branche comme
Some(name)sort uneStringet que vous utilisez l’original ensuite. Faites plutôt lematchsur&nickname. - E0507 : cannot move out of a borrowed value (impossible de déplacer hors d’une valeur empruntée), quand une branche essaie de sortir une
Stringde quelque chose que vous avez seulement emprunté. - Toutes les erreurs du compilateur : la liste complète des pages d’erreurs.
Petit quiz
Section intitulée « Petit quiz »Petit quiz
1. Vous ajoutez une variante Refunded à un enum. Que fait le compilateur avec vos match existants qui nomment chaque variante ?
2. Vous faites un match sur un &str avec "yes" et "no". Comment corriger E0004 ?
3. Quel est l'inconvénient de _ => dans un match sur votre propre enum ?
Score : 0 / 3
Pour aller plus loin
Section intitulée « Pour aller plus loin »- E0004 — Rust error codes index (en anglais) : l’explication officielle de cette erreur.
- E0005 — Rust error codes index (en anglais) : l’erreur sœur, pour les motifs dans
let. - The match Control Flow Construct — The Rust Book (en anglais) : le chapitre qui explique pourquoi les
matchsont exhaustifs, et le joker_. - Refutability — The Rust Book (en anglais) : quand un motif peut ne pas correspondre, et où c’est autorisé.
- match — Rust by Example (en anglais) : de courts exemples exécutables de plages, de
|et de_dans les branches d’unmatch.