Pular para o conteúdo

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

O E0004 quer dizer que o seu match esqueceu pelo menos um valor possível, e o Rust se recusa a adivinhar o que deveria acontecer nesse caso. É um dos erros mais simpáticos do Rust: o compilador diz exatamente qual caso está faltando e até escreve a linha para você. Esta página mostra a correção primeiro e depois explica por que essa verificação é um dos melhores recursos do Rust. Para os outros erros desta série, veja Erros do compilador 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!(),
|

Em português: “padrões não exaustivos: OrderStatus::Delivered não foi coberto”. O compilador mostra onde o enum foi definido, marca a variante esquecida e sugere o braço que falta.

Este é o programa que gera o erro:

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",
// ❌ nada para Delivered
}
}
fn main() {
println!("{}", describe(OrderStatus::Delivered));
}

O texto entre as crases muda conforme o seu código. Você pode ver `None` not covered, `&_` not covered, `0_u8` and `4_u8..=u8::MAX` not covered ou `(true, false)` and `(false, true)` not covered. É sempre o mesmo erro: o Rust está listando os valores para os quais o seu match não tem braço.

Leia a mensagem de cima para baixo:

  1. Linha 8: o ^^^^^^ aponta para o valor em que você está fazendo match, status.
  2. “pattern OrderStatus::Delivered not covered”: este é o caso que você esqueceu.
  3. A note pula para a definição do enum e marca a variante esquecida na linha 4.
  4. O help mostra exatamente o braço que você deve acrescentar. todo!() é só um marcador; você troca por código de verdade.

Pense numa máquina de venda com três botões, mas só dois ligados. Apertar o terceiro faria… o quê? O Rust não deixa você entregar uma máquina com um botão morto. Todo botão precisa de uma resposta, nem que a resposta seja “fora de serviço”.

Um match em Rust precisa ser exaustivo: para cada valor que o tipo pode ter, exatamente um braço tem que servir. Se faltasse um braço, o programa chegaria a um ponto sem código para rodar. Outras linguagens deixam passar em silêncio, devolvem um valor padrão ou quebram em tempo de execução. O Rust pega isso antes de o programa rodar. A lição de match cobre o básico.

A grande vantagem aparece depois, quando o seu código cresce. Imagine que você acrescenta uma variante a um enum usado em dez match espalhados pelo projeto:

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

Agora o compilador dá um E0004 para cada match que precisa ser atualizado:

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

Cada linha aponta um match que ainda não trata Returned. É uma lista de tarefas escrita para você. Não precisa vasculhar o código torcendo para ter achado todos os lugares. É por isso que quem tem experiência com Rust gosta tanto de enums: dá para mudá-los sem medo.

Escolha esta para os seus próprios enums. Na maioria das vezes, é a correção certa, e é exatamente o que o help: do rustc sugere. Acrescente o braço e troque o todo!() pelo que deve acontecer de verdade:

enum OrderStatus {
Pending,
Shipped,
Delivered,
}
fn describe(status: OrderStatus) -> &'static str {
match status {
OrderStatus::Pending => "waiting to be packed",
OrderStatus::Shipped => "on its way",
OrderStatus::Delivered => "at your door", // ✅ todos os casos tratados
}
}
fn main() {
println!("{}", describe(OrderStatus::Pending));
println!("{}", describe(OrderStatus::Delivered));
}

Saída:

waiting to be packed
at your door
▶ Experimente você mesmo

(Os textos ficaram em inglês para combinar com a mensagem de erro lá de cima.) Se várias variantes devem fazer a mesma coisa, junte-as com | num braço só: OrderStatus::Shipped | OrderStatus::Delivered => false. Você continua nomeando cada variante, então o compilador continua avisando sobre as novas. O mesmo vale para Option: se o erro diz `None` not covered, acrescente um braço None => .... Veja Option.

Correção 2: Use intervalos e _ para números e texto

Seção intitulada “Correção 2: Use intervalos e _ para números e texto”

Escolha esta quando você faz match em inteiros, char ou strings. Um u8 tem 256 valores possíveis e um &str tem infinitos, então não dá para listar todos. Use intervalos como 1..=9 para grupos de números e termine com _ (ou um nome como other) para “todo o resto”:

fn grade(score: u8) -> char {
match score {
90..=100 => 'A',
75..=89 => 'B',
50..=74 => 'C',
0..=49 => 'F',
_ => '?', // de 101 a 255: não é uma nota de verdade
}
}
fn run(command: &str) {
match command {
"start" => println!("Iniciando o cronômetro"),
"stop" => println!("Parando o cronômetro"),
other => println!("Comando desconhecido: {other}"),
}
}
fn main() {
println!("{} {} {}", grade(95), grade(62), grade(150));
run("start");
run("pause");
}

Saída:

A C ?
Iniciando o cronômetro
Comando desconhecido: pause
▶ Experimente você mesmo

Para strings, o rustc diz isso com todas as letras: `&str` cannot be matched exhaustively, so a wildcard `_` is necessary (não dá para cobrir todos os valores de &str, então o curinga _ é obrigatório). Para números, o Rust confere os seus intervalos com precisão. Se eles cobrem mesmo todos os valores (por exemplo, de 0..=49 até 90..=u8::MAX), você nem precisa do _. Mais em Intervalos.

Correção 3: Use if let ou let else quando só um caso importa

Seção intitulada “Correção 3: Use if let ou let else quando só um caso importa”

Escolha esta quando você só quer agir em um padrão e ignorar o resto. Um match completo com um braço vazio de “não faz nada” funciona, mas o if let diz isso com mais clareza. O let else serve para “tire o valor de dentro ou saia da função mais cedo”:

fn greet(nickname: Option<&str>) {
if let Some(name) = nickname {
println!("Oi, {name}!");
}
// sem apelido: não faz nada
}
fn first_word_len(text: &str) -> usize {
let Some(word) = text.split_whitespace().next() else {
return 0; // texto vazio: sai mais cedo
};
word.len()
}
fn main() {
greet(Some("Ferris"));
greet(None);
println!("{}", first_word_len("hello world"));
println!("{}", first_word_len(" "));
}

Saída:

Oi, Ferris!
5
0
▶ Experimente você mesmo

O texto "hello world" ficou em inglês porque o programa conta os bytes da palavra com .len(), e letras acentuadas ocupam 2 bytes. O bloco else de um let else precisa sair do bloco atual (return, break, continue ou um panic). Veja if let e let-else.

Correção 4: Um _ => para pegar o resto (com um custo)

Seção intitulada “Correção 4: Um _ => para pegar o resto (com um custo)”

Escolha esta quando a maioria das variantes de um enum faz mesmo a mesma coisa. O _ casa com qualquer coisa que os braços de cima não pegaram:

enum Key {
Up,
Down,
Left,
Right,
Enter,
Escape,
}
fn handle(key: Key) {
match key {
Key::Enter => println!("Confirmar"),
Key::Escape => println!("Cancelar"),
_ => println!("Mover o cursor"),
}
}
fn main() {
handle(Key::Enter);
handle(Key::Left);
handle(Key::Escape);
}

Saída:

Confirmar
Mover o cursor
Cancelar
▶ Experimente você mesmo

Seja sincero sobre o custo: o _ desliga a verificação de Por que o Rust faz isso. Se mais tarde você acrescentar Key::Delete, o compilador fica quieto, e apertar Delete move o cursor. É um bug que o Rust poderia ter pegado para você. Nos seus próprios enums, prefira listar as variantes com | (Key::Up | Key::Down | Key::Left | Key::Right => ...). Guarde o _ para números, strings e enums de outros crates.

Option sem None. match nickname { Some(name) => ... } dá `None` not covered. Correção: acrescente um braço None =>, ou use if let Some(name) = nickname se não há nada a fazer para None.

Tuplas e booleanos. Fazer match em dois bool ao mesmo tempo dá quatro combinações, e é fácil esquecer alguma:

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

Correção: acrescente os braços que faltam, ou use _ dentro da tupla, como (false, _) => println!("Faça login"), que cobre os dois casos de “não está logado”. Veja Vários valores de uma vez.

Um padrão no let. Escrever let Some(score) = saved; é um let que pode não casar. Esse é um erro irmão, o E0005, com a mesma ideia:

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!() };
| ++++++++++++++++

Em português: “padrão refutável num binding local”. Um let comum precisa de um padrão que sempre casa, e Some(score) não casa com None; o rustc sugere usar let...else. Correção: let Some(score) = saved else { return; };, ou if let, como na Correção 3. Veja Padrões no let.

Enums de outros crates. Alguns enums, como std::io::ErrorKind, são marcados com #[non_exhaustive]. Os autores podem acrescentar variantes no futuro, então mesmo que você liste todas as variantes que existem hoje, o rustc diz `_` not covered. Correção: acrescente um braço _ =>. Aqui ele é obrigatório, não um atalho.

  • E0005 (refutable pattern in local binding): a mesma verificação de “not covered”, para let em vez de match. Mostrado acima.
  • E0308: tipos incompatíveis, por exemplo quando os braços de um match devolvem tipos diferentes ("texto" num braço, um número em outro).
  • E0382: borrow of partially moved value (empréstimo de um valor parcialmente movido), quando um braço como Some(name) move uma String para fora e você usa o original depois. Faça o match em &nickname.
  • E0507: cannot move out of a borrowed value, quando um braço tenta tirar uma String de algo que você só pegou emprestado.
  • Todos os erros do compilador: a lista completa de páginas de erro.

Teste o que aprendeu

1. Você acrescenta uma variante Refunded a um enum. O que o compilador faz com os match que já existem e listam cada variante pelo nome?

2. Você faz match de um &str com "yes" e "no". Como corrigir o E0004?

3. Qual é a desvantagem de usar _ => num match do seu próprio enum?

Pontuação: 0 / 3