Aller au contenu

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.

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 covered

Deux mots font tout le travail ici :

  • Un motif réfutable (« refutable ») peut ne pas correspondre. Some(top) échoue quand la valeur vaut None. Ok(age) échoue sur Err. [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.

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.

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 : 95
Port : 3000
'abc' n'est pas un port valide, on utilise 8080
Port : 8080
▶ Essayez par vous-même

Ne 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 : 42
Impossible de lire 'quarante-deux' : invalid digit found in string
hôte = localhost, port = 8080
'localhost' devrait ressembler à hôte:port
Salut, inconnu !
▶ Essayez par vous-même

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 ici
fn 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 : 0
Volume : 50
Ok(150)
Err(ParseIntError { kind: InvalidDigit })
▶ Essayez par vous-même

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=0
localhost:8080 en (3, -1)
thème -> sombre
▶ Essayez par vous-même

C’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.

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.

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