Salta ai contenuti

Errore Rust E0004: non-exhaustive patterns not covered (match incompleto)

E0004 significa che il tuo match ha dimenticato almeno un valore possibile, e Rust si rifiuta di indovinare cosa dovrebbe succedere in quel caso. È uno degli errori più gentili di Rust: il compilatore ti dice esattamente quale caso manca e ti scrive perfino la riga. Questa pagina ti mostra prima la soluzione e poi ti spiega perché questo controllo è una delle cose migliori di Rust. Per gli altri errori di questa serie, vedi Errori del compilatore Rust.

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!(),
|

Il compilatore dice: «pattern non esaustivi: OrderStatus::Delivered non è coperto». Questo è il programma che lo produce:

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));
}

Il testo tra i backtick cambia a seconda del tuo codice. Potresti vedere `None` not covered, `&_` not covered, `0_u8` and `4_u8..=u8::MAX` not covered oppure `(true, false)` and `(false, true)` not covered. È sempre lo stesso errore: Rust elenca i valori per cui il tuo match non ha un ramo.

Leggi il messaggio dall’alto verso il basso:

  1. Riga 8: i ^^^^^^ indicano il valore su cui fai il match, status.
  2. «pattern OrderStatus::Delivered not covered»: questo è il caso che hai dimenticato.
  3. La nota salta alla definizione dell’enum e segna la variante dimenticata alla riga 4.
  4. L’help mostra esattamente il ramo da aggiungere. todo!() è un segnaposto; lo sostituisci con codice vero.

Pensa a un distributore automatico con tre pulsanti, di cui solo due collegati. Premere il terzo farebbe… cosa? Rust non ti lascia consegnare una macchina con un pulsante morto. Ogni pulsante ha bisogno di una risposta, anche se la risposta è «fuori servizio».

Un match in Rust deve essere esaustivo: per ogni valore che il tipo può contenere, deve applicarsi esattamente un ramo. Se mancasse un ramo, il programma arriverebbe in un punto senza codice da eseguire. Altri linguaggi in quel caso proseguono in silenzio, restituiscono un valore predefinito o vanno in crash durante l’esecuzione. Rust lo scopre prima ancora che il programma parta. La lezione su match copre le basi.

Il vero vantaggio arriva dopo, quando il tuo codice cresce. Supponi di aggiungere una nuova variante a un enum usato in dieci match sparsi nel progetto:

enum OrderStatus {
Pending,
Shipped,
Delivered,
Returned, // nuova!
}

Ora il compilatore ti dà un E0004 per ogni match da aggiornare:

error[E0004]: non-exhaustive patterns: `&OrderStatus::Returned` not covered
--> src/main.rs:9:11
error[E0004]: non-exhaustive patterns: `&OrderStatus::Returned` not covered
--> src/main.rs:17:11

È una lista di cose da fare scritta per te. Non devi frugare in tutto il codice sperando di trovare ogni punto. Ecco perché chi ha esperienza con Rust ama così tanto gli enum: puoi cambiarli senza paura.

Scegli questa per i tuoi enum. Nella maggior parte dei casi è la soluzione giusta, ed è proprio quella che suggerisce l’help: di rustc. Aggiungi il ramo, e sostituisci todo!() con ciò che deve succedere davvero:

enum OrderStatus {
Pending,
Shipped,
Delivered,
}
fn describe(status: OrderStatus) -> &'static str {
match status {
OrderStatus::Pending => "in attesa di imballaggio",
OrderStatus::Shipped => "in viaggio",
OrderStatus::Delivered => "alla tua porta", // ✅ tutti i casi gestiti
}
}
fn main() {
println!("{}", describe(OrderStatus::Pending));
println!("{}", describe(OrderStatus::Delivered));
}

Output:

in attesa di imballaggio
alla tua porta
▶ Provalo tu

Se più varianti devono fare la stessa cosa, uniscile con | in un solo ramo: OrderStatus::Shipped | OrderStatus::Delivered => false. Nomini comunque ogni variante, così il compilatore può ancora avvisarti di quelle nuove. Lo stesso vale per Option: se l’errore dice `None` not covered, aggiungi un ramo None => .... Vedi Option.

Soluzione 2: Usa intervalli e _ per numeri e testo

Sezione intitolata “Soluzione 2: Usa intervalli e _ per numeri e testo”

Scegli questa quando fai match su interi, char o stringhe. Un u8 ha 256 valori possibili e un &str infiniti, quindi non puoi elencarli tutti. Usa intervalli come 1..=9 per gruppi di numeri, e chiudi con _ (o con un nome come other) per «tutto il resto»:

fn grade(score: u8) -> char {
match score {
90..=100 => 'A',
75..=89 => 'B',
50..=74 => 'C',
0..=49 => 'F',
_ => '?', // da 101 a 255: non è un punteggio vero
}
}
fn run(command: &str) {
match command {
"start" => println!("Avvio il timer"),
"stop" => println!("Fermo il timer"),
other => println!("Comando sconosciuto: {other}"),
}
}
fn main() {
println!("{} {} {}", grade(95), grade(62), grade(150));
run("start");
run("pause");
}

Output:

A C ?
Avvio il timer
Comando sconosciuto: pause
▶ Provalo tu

Per le stringhe rustc lo dice direttamente: `&str` cannot be matched exhaustively, so a wildcard `_` is necessary (un &str non si può coprire in modo esaustivo, quindi serve un _). Per i numeri, Rust controlla i tuoi intervalli con precisione. Se coprono davvero ogni valore (per esempio da 0..=49 fino a 90..=u8::MAX), il _ non serve affatto. Trovi di più in Un intervallo di valori.

Soluzione 3: Usa if let o let else quando ti interessa un solo caso

Sezione intitolata “Soluzione 3: Usa if let o let else quando ti interessa un solo caso”

Scegli questa quando vuoi agire su un solo pattern e ignorare il resto. Un match completo con un ramo vuoto «non fare niente» funziona, ma if let lo dice in modo più chiaro. let else serve per «tira fuori il valore, oppure esci subito dalla funzione»:

fn greet(nickname: Option<&str>) {
if let Some(name) = nickname {
println!("Ciao, {name}!");
}
// nessun soprannome: non fare niente
}
fn first_word_len(text: &str) -> usize {
let Some(word) = text.split_whitespace().next() else {
return 0; // testo vuoto: esci subito
};
word.len()
}
fn main() {
greet(Some("Ferris"));
greet(None);
println!("{}", first_word_len("ciao mondo"));
println!("{}", first_word_len(" "));
}

Output:

Ciao, Ferris!
4
0
▶ Provalo tu

Il blocco else di un let else deve uscire dal blocco corrente (return, break, continue o un panic). Vedi if let e let-else.

Soluzione 4: Un _ => per tutto il resto (con un compromesso)

Sezione intitolata “Soluzione 4: Un _ => per tutto il resto (con un compromesso)”

Scegli questa quando la maggior parte delle varianti di un enum fa davvero la stessa cosa. _ corrisponde a tutto ciò che i rami sopra non hanno preso:

enum Key {
Up,
Down,
Left,
Right,
Enter,
Escape,
}
fn handle(key: Key) {
match key {
Key::Enter => println!("Conferma"),
Key::Escape => println!("Annulla"),
_ => println!("Sposta il cursore"),
}
}
fn main() {
handle(Key::Enter);
handle(Key::Left);
handle(Key::Escape);
}

Output:

Conferma
Sposta il cursore
Annulla
▶ Provalo tu

Sii onesto sul costo: _ spegne il controllo descritto in Perché Rust si comporta così. Se più avanti aggiungi Key::Delete, il compilatore resta in silenzio, e premere Canc sposta il cursore. È un bug che Rust avrebbe potuto scoprire per te. Per i tuoi enum, preferisci elencare le varianti con | (Key::Up | Key::Down | Key::Left | Key::Right => ...). Tieni _ per numeri, stringhe ed enum di altri crate.

Option senza None. match nickname { Some(name) => ... } dà `None` not covered. Soluzione: aggiungi un ramo None =>, oppure usa if let Some(name) = nickname se per None non c’è niente da fare.

Tuple e booleani. Fare match su due bool insieme dà quattro combinazioni, ed è facile dimenticarne qualcuna:

match (logged_in, is_admin) {
(true, true) => println!("Welcome, admin"),
(false, false) => println!("Please log in"),
} // ❌ patterns `(true, false)` and `(false, true)` not covered

Soluzione: aggiungi i rami mancanti, oppure usa _ dentro la tupla, come (false, _) => println!("Please log in"), che copre entrambi i casi «non autenticato». Vedi Più valori in un colpo solo.

Un pattern in un let. Scrivere let Some(score) = saved; è un let che potrebbe non corrispondere. È un errore gemello, E0005, con la stessa idea:

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 variant
help: you might want to use `let...else` to handle the variant that isn't matched
|
3 | let Some(score) = saved else { todo!() };
| ++++++++++++++++

Soluzione: let Some(score) = saved else { return; };, oppure if let, come nella Soluzione 3. Vedi Pattern nei let.

Enum di altri crate. Alcuni enum, come std::io::ErrorKind, sono marcati #[non_exhaustive]. I loro autori potrebbero aggiungere varianti in futuro, quindi anche se elenchi tutte quelle che vedi oggi, rustc dice `_` not covered. Soluzione: aggiungi un ramo _ =>. Qui è obbligatorio, non una scorciatoia.

  • E0005 (refutable pattern in local binding): lo stesso controllo «not covered», per let invece che per match. L’hai visto qui sopra.
  • E0308: mismatched types, per esempio quando i rami di un match restituiscono tipi diversi ("testo" in un ramo, un numero in un altro).
  • E0382: borrow of partially moved value, quando un ramo come Some(name) sposta fuori una String e poi usi l’originale. Fai invece il match su &nickname.
  • E0507: cannot move out of a borrowed value, quando un ramo prova a prendere una String da qualcosa che hai solo in prestito.
  • Tutti gli errori del compilatore: l’elenco completo delle pagine sugli errori.

Verifica veloce

1. Aggiungi una variante Refunded a un enum. Cosa fa il compilatore con i tuoi match esistenti che elencano ogni variante per nome?

2. Fai il match di un &str con "yes" e "no". Come risolvi E0004?

3. Qual è lo svantaggio di _ => in un match su un tuo enum?

Punteggio: 0 / 3