Pular para o conteúdo

Erro E0005 do Rust: refutable pattern in local binding (let que pode falhar)

O E0005 quer dizer que você escreveu um let cujo padrão pode não casar, como let Some(x) = value; quando value pode ser None. O Rust precisa saber o que acontece nesse caso, e um let simples não tem onde dizer isso. A correção é dar um destino para o caso “não casou”: normalmente let ... else, if let ou match. É um erro muito comum quando você começa a trabalhar com Option e Result, e leva um minuto para corrigir. Para os outros erros desta série, veja Erros do compilador Rust.

error[E0005]: refutable pattern in local binding
--> src/main.rs:4:9
|
4 | let Some(top) = best; // ❌ e se a lista estivesse vazia?
| ^^^^^^^^^ 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!() }; // ❌ e se a lista estivesse vazia?
| ++++++++++++++++

Em português: “padrão refutável numa declaração local”, ou seja, um padrão que pode falhar dentro de um let. O rustc avisa que o caso None não está coberto, lembra que o let exige um padrão que sempre casa e sugere usar let...else para tratar o caso que falta.

Este é o programa que gera o erro:

fn main() {
let scores = vec![72, 95, 88];
let best = scores.iter().max().copied();
let Some(top) = best; // ❌ e se a lista estivesse vazia?
println!("Top score: {top}");
}

Você vai ver o mesmo erro com outros padrões. Só muda a parte “not covered” (não coberto):

3 | let Ok(age) = input.parse::<i32>();
| ^^^^^^^ pattern `Err(_)` not covered
3 | let [host, port] = parts.as_slice();
| ^^^^^^^^^^^^ patterns `&[]`, `&[_]` and `&[_, _, _, ..]` not covered

No primeiro, falta o caso Err(_). No segundo, faltam a slice vazia, a de um item e a de três ou mais itens.

Duas palavras fazem todo o trabalho aqui:

  • Um padrão refutável pode falhar. Some(top) falha quando o valor é None. Ok(age) falha com Err. [host, port] falha quando a slice não tem exatamente dois itens.
  • Um padrão irrefutável sempre casa. Um nome simples, como em let top = best;, sempre funciona, e tuplas e structs também, como em let (x, y) = point;.

Um let precisa sempre dar certo, porque a linha seguinte usa as variáveis que ele cria. Se best fosse None, o que seria top na linha seguinte? Não há resposta, então o Rust para você. O rótulo “not covered” lista exatamente os casos que você esqueceu: None, Err(_) ou, para a slice, “vazia”, “um item” e “três ou mais itens”.

Pense num armário de retirada de encomendas. let Some(top) = best; é como ir embora certo de que tem um pacote lá dentro, sem abrir a porta. O Rust faz você decidir o que fazer se o armário estiver vazio.

O let, os parâmetros de funções e os laços for precisam de padrões que não podem falhar. match, if let, while let e let ... else aceitam padrões que podem falhar, porque cada um deles tem um lugar para o outro caso. É a mesma regra do E0004, em que um match precisa cobrir todos os valores. Aqui o “match” é um let com um braço só.

Em muitas linguagens, ler um valor que não existe dá null, undefined ou uma falha em tempo de execução. Em Rust, uma lista vazia, um número inválido num campo de texto ou uma configuração que falta são casos normais, que você trata no código. Option e Result deixam esses casos visíveis, e o E0005 garante que você não pule nenhum sem querer. Veja Padrões no let e Option.

Escolha esta quando o resto da função precisa do valor e, sem ele, não há nada de útil para fazer. É o que o help: do rustc sugere. Você mantém o seu padrão e acrescenta um bloco else. Esse bloco precisa sair: return, break, continue ou um panic.

fn port_from(text: &str) -> u16 {
// tira o número de dentro, ou sai da função mais cedo
let Ok(port) = text.trim().parse::<u16>() else {
println!("'{text}' não é uma porta válida, usando 8080");
return 8080;
};
port
}
fn main() {
let scores = vec![72, 95, 88];
let Some(top) = scores.iter().max() else {
println!("Ainda não há notas");
return;
};
println!("Maior nota: {top}");
println!("Porta: {}", port_from("3000"));
println!("Porta: {}", port_from("abc"));
}

Saída:

Maior nota: 95
Porta: 3000
'abc' não é uma porta válida, usando 8080
Porta: 8080
▶ Experimente você mesmo

Não deixe o else { todo!() } do rustc do jeito que está. O todo!() dá panic quando roda, então troque por uma saída de verdade. Mais em let-else.

Correção 2: match ou if let quando os dois casos fazem algo

Seção intitulada “Correção 2: match ou if let quando os dois casos fazem algo”

Escolha esta quando o caso “não casou” tem o próprio trabalho a fazer, como mostrar o erro, ou quando o código continua de qualquer jeito. O match deixa você tratar todos os casos e usar o valor do erro. Ele também é o jeito de desmontar uma slice quando você não sabe o tamanho dela. Use if let quando só um caso importa:

fn main() {
// match: trata os dois resultados e usa o erro
for input in ["42", "quarenta e dois"] {
match input.parse::<i32>() {
Ok(age) => println!("Idade: {age}"),
Err(e) => println!("Não consegui ler '{input}': {e}"),
}
}
// uma slice pode ter qualquer tamanho: faça match no formato dela
for address in ["localhost:8080", "localhost"] {
let parts: Vec<&str> = address.split(':').collect();
match parts.as_slice() {
[host, port] => println!("host = {host}, porta = {port}"),
_ => println!("'{address}' deveria ter o formato host:porta"),
}
}
// if let: só faz algo quando existe um valor
let nickname: Option<&str> = None;
if let Some(name) = nickname {
println!("Oi, {name}!");
} else {
println!("Oi, pessoa desconhecida!");
}
}

Saída:

Idade: 42
Não consegui ler 'quarenta e dois': invalid digit found in string
host = localhost, porta = 8080
'localhost' deveria ter o formato host:porta
Oi, pessoa desconhecida!
▶ Experimente você mesmo

invalid digit found in string (dígito inválido encontrado na string) é a mensagem do próprio Rust, por isso fica em inglês. O braço _ pega todos os outros tamanhos. Sem ele, o match daria o E0004, com a mesma lista de “not covered”. Veja if let e match.

Correção 3: Um valor padrão com unwrap_or, ou repasse com ?

Seção intitulada “Correção 3: Um valor padrão com unwrap_or, ou repasse com ?”

Escolha esta quando existe um valor padrão que faz sentido, ou quando quem chamou a função deve lidar com o problema. unwrap_or(x) entrega o valor, ou x se não houver nenhum. O ? funciona dentro de uma função que retorna Result (ou Option): ele devolve o erro para quem chamou e encerra a função.

use std::num::ParseIntError;
// ? repassa o erro para quem chamou, em vez de tratá-lo aqui
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() {
// um valor padrão quando não há nada
let scores: Vec<i32> = Vec::new();
let top = scores.iter().max().copied().unwrap_or(0);
println!("Maior nota: {top}");
let volume = "alto".parse::<u8>().unwrap_or(50);
println!("Volume: {volume}");
println!("{:?}", total_minutes("2", "30"));
println!("{:?}", total_minutes("2", "meia"));
}

Saída:

Maior nota: 0
Volume: 50
Ok(150)
Err(ParseIntError { kind: InvalidDigit })
▶ Experimente você mesmo

Se você tem certeza de que o valor existe (por exemplo, um número que você mesmo escreveu no código), .expect("motivo") também serve. Ele dá panic com a sua mensagem se você estiver errado, então guarde-o para casos que realmente não podem falhar. Usar ? num main que não retorna nada dá o E0277; veja O operador ? e Atalhos: unwrap_or.

Correção 4: Use um tipo cujo padrão não pode falhar

Seção intitulada “Correção 4: Use um tipo cujo padrão não pode falhar”

Escolha esta quando os dados sempre têm o mesmo formato, e o padrão só falha por causa do tipo que você escolheu. Uma slice ou um Vec pode ter qualquer tamanho, mas um array como [u8; 3] sempre tem exatamente três itens, então let [r, g, b] = color; sempre casa. Tuplas e structs também sempre casam. E o split_once entrega as duas metades numa tupla só, então você lida com um único Option em vez de uma slice de tamanho desconhecido:

struct Point {
x: i32,
y: i32,
}
fn main() {
// arrays têm tamanho fixo: este padrão sempre casa
let color: [u8; 3] = [255, 140, 0];
let [r, g, b] = color;
println!("r={r} g={g} b={b}");
// tuplas e structs também sempre casam
let (host, port) = ("localhost", 8080);
let Point { x, y } = Point { x: 3, y: -1 };
println!("{host}:{port} em ({x}, {y})");
// split_once retorna um Option só, com as duas metades
let Some((key, value)) = "theme=dark".split_once('=') else {
return;
};
println!("{key} -> {value}");
}

Saída:

r=255 g=140 b=0
localhost:8080 em (3, -1)
theme -> dark
▶ Experimente você mesmo

O mesmo vale para os seus próprios enums: let Shape::Circle(radius) = shape; falha com pattern `Shape::Square(_)` not covered. Se o valor realmente só pode ser um círculo, talvez ele devesse ser uma struct Circle em vez de um enum. Veja Arrays e Structs.

Um laço for sobre Options. for Some(temp) in readings dá “refutable pattern in for loop binding” (padrão refutável na variável do laço for). Correção: for temp in readings.iter().flatten() pula os None, ou percorra todos os itens e use if let Some(t) = reading lá dentro.

Um parâmetro de função. fn greet(Some(name): Option<&str>) dá “refutable pattern in function argument” (padrão refutável no argumento da função). Correção: receba name: Option<&str> e faça um match dentro da função, ou receba name: &str e deixe quem chama decidir o que fazer com None.

Ler uma entrada. let Ok(n) = line.trim().parse::<i32>(); ao ler o que o usuário digitou ou argumentos de linha de comando. Correção: let Ok(n) = ... else { println!("Digite um número"); continue; }; dentro do laço de entrada, ou um match para mostrar o erro.

Teste o que aprendeu

1. Qual let dá o E0005?

2. O que o bloco else de um let ... else precisa fazer?

3. Você precisa de um número a partir de um texto, e 0 é um bom valor padrão se o texto não for um número. Qual é a correção mais curta?

Pontuação: 0 / 3