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.
A mensagem de erro
Seção intitulada “A mensagem de erro”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.
O que significa
Seção intitulada “O que significa”Leia a mensagem de cima para baixo:
- Linha 8: o
^^^^^^aponta para o valor em que você está fazendo match,status. - “pattern
OrderStatus::Deliverednot covered”: este é o caso que você esqueceu. - A note pula para a definição do enum e marca a variante esquecida na linha 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”.
Por que o Rust faz isso
Seção intitulada “Por que o Rust faz isso”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:11error[E0004]: non-exhaustive patterns: `&OrderStatus::Returned` not covered --> src/main.rs:17:11Cada 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.
Como corrigir
Seção intitulada “Como corrigir”Correção 1: Acrescente o braço que falta
Seção intitulada “Correção 1: Acrescente o braço que falta”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 packedat your door(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ômetroComando desconhecido: pausePara 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!50O 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:
ConfirmarMover o cursorCancelarSeja 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.
Onde você costuma ver esse erro
Seção intitulada “Onde você costuma ver esse erro”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 coveredCorreçã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 varianthelp: 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.
Erros relacionados
Seção intitulada “Erros relacionados”- E0005 (refutable pattern in local binding): a mesma verificação de “not covered”, para
letem vez dematch. 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 umaStringpara 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
Stringde algo que você só pegou emprestado. - Todos os erros do compilador: a lista completa de páginas de erro.
Teste o que aprendeu
Seção intitulada “Teste o que aprendeu”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
Para saber mais
Seção intitulada “Para saber mais”- E0004 — Rust error codes index (em inglês): a explicação oficial deste erro.
- E0005 — Rust error codes index (em inglês): o erro irmão, para padrões no
let. - The match Control Flow Construct — The Rust Book (em inglês): o capítulo que explica por que o match é exaustivo e o curinga
_. - Refutability — The Rust Book (em inglês): quando um padrão pode não casar e onde isso é permitido.
- match — Rust by Example (em inglês): exemplos curtos para rodar com intervalos,
|e_nos braços do match.