Erreur Rust E0005 : refutable pattern in local binding (let qui peut échouer)
E0005 veut dire que vous avez écrit un let dont le motif (pattern) peut ne pas correspondre, comme let Some(x) = value; alors que value peut valoir None. Rust a besoin de savoir ce qui se passe dans ce cas, et un simple let n’a aucun endroit pour le dire. La solution : donner une place au cas « ça ne correspond pas », en général avec let ... else, if let ou match. C’est une erreur très courante quand vous commencez à travailler avec Option et Result, et elle se corrige en une minute. 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[E0005]: refutable pattern in local binding --> src/main.rs:4:9 |4 | let Some(top) = best; // ❌ what if the list was empty? | ^^^^^^^^^ pattern `None` not covered | = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant = note: for more information, visit https://doc.rust-lang.org/book/ch19-02-refutability.html = note: the matched value is of type `Option<i32>`help: you might want to use `let...else` to handle the variant that isn't matched |4 | let Some(top) = best else { todo!() }; // ❌ what if the list was empty? | ++++++++++++++++Le compilateur vous dit : « motif réfutable dans une liaison locale ». Le motif None n’est pas couvert, et un let exige un motif « irréfutable », comme une struct ou une enum à une seule variante. L’aide (help:) vous propose let...else pour traiter la variante qui ne correspond pas.
Voici le programme qui la provoque :
fn main() { let scores = vec![72, 95, 88]; let best = scores.iter().max().copied(); let Some(top) = best; // ❌ what if the list was empty? println!("Top score: {top}");}(Le commentaire demande : et si la liste était vide ?)
Vous verrez la même erreur avec d’autres motifs. Seule la partie « not covered » (non couvert) change :
3 | let Ok(age) = input.parse::<i32>(); | ^^^^^^^ pattern `Err(_)` not covered
3 | let [host, port] = parts.as_slice(); | ^^^^^^^^^^^^ patterns `&[]`, `&[_]` and `&[_, _, _, ..]` not coveredCe que ça veut dire
Section intitulée « Ce que ça veut dire »Deux mots font tout le travail ici :
- Un motif réfutable (« refutable ») peut ne pas correspondre.
Some(top)échoue quand la valeur vautNone.Ok(age)échoue surErr.[host, port]échoue quand la slice n’a pas exactement deux éléments. - Un motif irréfutable (« irrefutable ») correspond toujours. Un simple nom comme
let top = best;marche toujours, tout comme les tuples et les structs :let (x, y) = point;.
Un let doit toujours réussir, car la ligne suivante utilise les variables qu’il crée. Si best valait None, que vaudrait top à la ligne suivante ? Il n’y a pas de réponse, alors Rust vous arrête. L’étiquette « not covered » liste exactement les cas oubliés : None, Err(_) ou, pour la slice, « vide », « un seul élément » et « trois éléments ou plus ».
Imaginez une consigne à colis. Écrire let Some(top) = best;, c’est repartir persuadé qu’un colis vous attend dans le casier, sans ouvrir la porte. Rust vous oblige à décider quoi faire si le casier est vide.
Pourquoi Rust fait ça
Section intitulée « Pourquoi Rust fait ça »let, les paramètres de fonction et les boucles for ont tous besoin de motifs qui ne peuvent pas échouer. match, if let, while let et let ... else acceptent des motifs qui peuvent échouer, parce que chacun d’eux a une place pour l’autre cas. C’est la même règle que pour E0004, où un match doit couvrir toutes les valeurs. Ici, le « match » est un let avec une seule branche.
Dans beaucoup de langages, lire une valeur absente vous donne null, undefined ou un plantage à l’exécution. En Rust, une liste vide, un nombre mal tapé dans un champ de texte ou un réglage manquant sont des cas normaux que vous traitez dans le code. Option et Result les rendent visibles, et E0005 s’assure que vous ne les sautez pas par accident. Voir Les motifs dans let et Option.
Comment corriger
Section intitulée « Comment corriger »Solution 1 : let ... else pour sortir tôt
Section intitulée « Solution 1 : let ... else pour sortir tôt »Choisissez-la quand le reste de la fonction a besoin de la valeur et que, sans elle, il n’y a rien d’utile à faire. C’est ce que suggère le help: de rustc. Vous gardez votre motif et vous ajoutez un bloc else. Ce bloc doit sortir : return, break, continue ou une panic.
fn port_from(text: &str) -> u16 { // récupérer le nombre, ou sortir de la fonction tout de suite let Ok(port) = text.trim().parse::<u16>() else { println!("'{text}' n'est pas un port valide, on utilise 8080"); return 8080; }; port}
fn main() { let scores = vec![72, 95, 88]; let Some(top) = scores.iter().max() else { println!("Pas encore de scores"); return; }; println!("Meilleur score : {top}");
println!("Port : {}", port_from("3000")); println!("Port : {}", port_from("abc"));}Sortie :
Meilleur score : 95Port : 3000'abc' n'est pas un port valide, on utilise 8080Port : 8080Ne gardez pas tel quel le else { todo!() } proposé par rustc. todo!() provoque une panic quand il s’exécute : remplacez-le par une vraie façon de sortir. Plus de détails dans let-else.
Solution 2 : match ou if let quand les deux cas font quelque chose
Section intitulée « Solution 2 : match ou if let quand les deux cas font quelque chose »Choisissez-la quand le cas « ça ne correspond pas » a son propre travail à faire, comme afficher l’erreur, ou quand le code continue dans les deux cas. match vous permet de traiter chaque cas et d’utiliser la valeur de l’erreur. C’est aussi la façon de décomposer une slice dont vous ne connaissez pas la longueur. Utilisez if let quand un seul cas compte :
fn main() { // match : traiter les deux résultats, et utiliser l'erreur for input in ["42", "quarante-deux"] { match input.parse::<i32>() { Ok(age) => println!("Âge : {age}"), Err(e) => println!("Impossible de lire '{input}' : {e}"), } }
// une slice peut avoir n'importe quelle longueur : on teste sa forme avec match for address in ["localhost:8080", "localhost"] { let parts: Vec<&str> = address.split(':').collect(); match parts.as_slice() { [host, port] => println!("hôte = {host}, port = {port}"), _ => println!("'{address}' devrait ressembler à hôte:port"), } }
// if let : agir seulement quand il y a une valeur let nickname: Option<&str> = None; if let Some(name) = nickname { println!("Salut, {name} !"); } else { println!("Salut, inconnu !"); }}Sortie :
Âge : 42Impossible de lire 'quarante-deux' : invalid digit found in stringhôte = localhost, port = 8080'localhost' devrait ressembler à hôte:portSalut, inconnu !Le message invalid digit found in string (chiffre invalide dans la chaîne) vient de Rust lui-même, il reste donc en anglais. La branche _ attrape toutes les autres longueurs. Sans elle, match vous donnerait E0004 avec la même liste « not covered ». Voir if let et match.
Solution 3 : une valeur par défaut avec unwrap_or, ou transmettre avec ?
Section intitulée « Solution 3 : une valeur par défaut avec unwrap_or, ou transmettre avec ? »Choisissez-la quand il existe une valeur par défaut raisonnable, ou quand c’est à l’appelant de gérer le problème. unwrap_or(x) vous donne la valeur, ou x s’il n’y en a pas. ? fonctionne dans une fonction qui renvoie un Result (ou une Option) : il renvoie l’erreur à l’appelant et arrête la fonction.
use std::num::ParseIntError;
// ? transmet l'erreur à l'appelant au lieu de la traiter icifn total_minutes(hours: &str, minutes: &str) -> Result<i32, ParseIntError> { let h = hours.parse::<i32>()?; let m = minutes.parse::<i32>()?; Ok(h * 60 + m)}
fn main() { // une valeur par défaut quand il n'y a rien let scores: Vec<i32> = Vec::new(); let top = scores.iter().max().copied().unwrap_or(0); println!("Meilleur score : {top}");
let volume = "fort".parse::<u8>().unwrap_or(50); println!("Volume : {volume}");
println!("{:?}", total_minutes("2", "30")); println!("{:?}", total_minutes("2", "demie"));}Sortie :
Meilleur score : 0Volume : 50Ok(150)Err(ParseIntError { kind: InvalidDigit })Si vous êtes sûr que la valeur est là (par exemple un nombre que vous venez d’écrire dans le code), .expect("raison") convient aussi. Il provoque une panic avec votre message si vous vous êtes trompé : gardez-le pour les cas qui ne peuvent vraiment pas échouer. Utiliser ? dans un main qui ne renvoie rien donne E0277 ; voir L’opérateur ? et Les raccourcis : unwrap_or.
Solution 4 : utiliser un type dont le motif ne peut pas échouer
Section intitulée « Solution 4 : utiliser un type dont le motif ne peut pas échouer »Choisissez-la quand les données ont toujours la même forme, et que le motif n’échoue qu’à cause du type que vous avez choisi. Une slice ou un Vec peut avoir n’importe quelle longueur, mais un tableau comme [u8; 3] a toujours exactement trois éléments, donc let [r, g, b] = color; correspond toujours. Les tuples et les structs correspondent toujours aussi. Et split_once vous donne les deux moitiés dans un seul tuple : vous n’avez plus qu’une Option à gérer, au lieu d’une slice de longueur inconnue :
struct Point { x: i32, y: i32,}
fn main() { // les tableaux ont une longueur fixe : ce motif correspond toujours let color: [u8; 3] = [255, 140, 0]; let [r, g, b] = color; println!("r={r} g={g} b={b}");
// les tuples et les structs correspondent toujours aussi let (host, port) = ("localhost", 8080); let Point { x, y } = Point { x: 3, y: -1 }; println!("{host}:{port} en ({x}, {y})");
// split_once renvoie une seule Option avec les deux moitiés let Some((key, value)) = "thème=sombre".split_once('=') else { return; }; println!("{key} -> {value}");}Sortie :
r=255 g=140 b=0localhost:8080 en (3, -1)thème -> sombreC’est pareil pour vos propres enums : let Shape::Circle(radius) = shape; échoue avec pattern `Shape::Square(_)` not covered. Si la valeur ne peut vraiment être qu’un cercle, elle devrait peut-être être une struct Circle plutôt qu’une enum. Voir Les tableaux et Les structs.
Où vous le verrez souvent
Section intitulée « Où vous le verrez souvent »Une boucle for sur des Option. for Some(temp) in readings donne « refutable pattern in for loop binding ». Correction : for temp in readings.iter().flatten() saute les None, ou bien parcourez chaque élément et utilisez if let Some(t) = reading à l’intérieur.
Un paramètre de fonction. fn greet(Some(name): Option<&str>) donne « refutable pattern in function argument ». Correction : prenez name: Option<&str> et faites un match dans la fonction, ou prenez name: &str et laissez l’appelant décider quoi faire de None.
Lire une saisie. let Ok(n) = line.trim().parse::<i32>(); quand vous lisez ce que tape l’utilisateur ou des arguments de ligne de commande. Correction : let Ok(n) = ... else { println!("Tapez un nombre, s'il vous plaît"); continue; }; dans la boucle de saisie, ou un match pour afficher l’erreur.
Erreurs liées
Section intitulée « Erreurs liées »- E0004 : non-exhaustive patterns : la même vérification « not covered », pour
matchau lieu delet. - E0308 : mismatched types : le motif ne correspond pas du tout au type, par exemple
let Some(x) = 5;. - E0277 : trait bound not satisfied : ce que vous obtenez en utilisant
?dans une fonction qui ne renvoie niResultniOption, comme un simplefn main(). - E0282 : type annotations needed : apparaît souvent à côté de
parse(), quand Rust ne sait pas quel type vous voulez. Ajoutez::<i32>comme dans les exemples ci-dessus. - Toutes les erreurs du compilateur : la liste complète des pages d’erreurs.
Petit quiz
Section intitulée « Petit quiz »Petit quiz
1. Quel let donne E0005 ?
2. Que doit faire le bloc else d'un let ... else ?
3. Vous voulez un nombre à partir d'un texte, et 0 est une bonne valeur par défaut si le texte n'est pas un nombre. Quelle est la correction la plus courte ?
Score : 0 / 3
Pour aller plus loin
Section intitulée « Pour aller plus loin »- E0005 — Rust error codes index (en anglais) : l’explication officielle de cette erreur.
- Refutability — The Rust Book (en anglais) : le chapitre vers lequel rustc renvoie, sur les motifs qui peuvent ou non échouer.
- All the Places Patterns Can Be Used — The Rust Book (en anglais) :
let,if let,while let, les bouclesforet les paramètres de fonction. - let-else — Rust by Example (en anglais) : de courts exemples exécutables de
let ... else. - if let — Rust by Example (en anglais) : quand
if letse lit mieux qu’unmatchcomplet.