Error E0005 de Rust: refutable pattern in local binding (let que puede fallar)
E0005 significa que escribiste un let cuyo patrón puede no coincidir, como let Some(x) = value; cuando value podría ser None. Rust necesita saber qué pasa en ese caso, y un let normal no tiene dónde decirlo. La solución es darle un sitio al caso “no coincidió”: normalmente let ... else, if let o match. Es un error muy común cuando empiezas a trabajar con Option y Result, y se arregla en un minuto. Para los demás errores de esta serie, mira Errores del compilador de Rust.
El mensaje de error
Sección titulada «El mensaje de error»error[E0005]: refutable pattern in local binding --> src/main.rs:4:9 |4 | let Some(top) = best; // ❌ ¿y si la lista estuviera vacía? | ^^^^^^^^^ 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!() }; // ❌ ¿y si la lista estuviera vacía? | ++++++++++++++++“Refutable pattern in local binding” quiere decir “patrón refutable (que puede fallar) en una variable local”. La etiqueta dice que el patrón None no está cubierto. Las notas explican que let necesita un patrón “irrefutable” (que siempre coincide), como un struct o un enum con una sola variante, y el help propone usar let...else para manejar la variante que no coincide.
Este es el programa que lo produce:
fn main() { let scores = vec![72, 95, 88]; let best = scores.iter().max().copied(); let Some(top) = best; // ❌ ¿y si la lista estuviera vacía? println!("Top score: {top}");}Verás el mismo error con otros patrones. Solo cambia la parte de “not covered” (no cubierto):
3 | let Ok(age) = input.parse::<i32>(); | ^^^^^^^ pattern `Err(_)` not covered
3 | let [host, port] = parts.as_slice(); | ^^^^^^^^^^^^ patterns `&[]`, `&[_]` and `&[_, _, _, ..]` not coveredEn el primero falta el caso Err(_). En el segundo faltan el slice vacío, el de un elemento y el de tres o más.
Qué significa
Sección titulada «Qué significa»Dos palabras hacen todo el trabajo:
- Un patrón refutable puede no coincidir.
Some(top)falla cuando el valor esNone.Ok(age)falla conErr.[host, port]falla cuando el slice no tiene exactamente dos elementos. - Un patrón irrefutable siempre coincide. Un nombre sin más, como
let top = best;, siempre funciona, igual que las tuplas y los structs, comolet (x, y) = point;.
Un let siempre tiene que salir bien, porque la línea siguiente usa las variables que crea. Si best fuera None, ¿qué valdría top en la línea siguiente? No hay respuesta, así que Rust te detiene. La etiqueta “not covered” enumera exactamente los casos que olvidaste: None, Err(_) o, para el slice, “vacío”, “un elemento” y “tres o más elementos”.
Piensa en un casillero de paquetería. let Some(top) = best; es como irte convencido de que hay un paquete dentro, sin abrir la puerta. Rust te obliga a decidir qué hacer si el casillero está vacío.
Por qué Rust hace esto
Sección titulada «Por qué Rust hace esto»let, los parámetros de funciones y los bucles for necesitan patrones que no puedan fallar. match, if let, while let y let ... else aceptan patrones que pueden fallar, porque cada uno tiene un sitio para el otro caso. Es la misma regla que en E0004, donde un match tiene que cubrir todos los valores. Aquí el “match” es un let con un solo brazo.
En muchos lenguajes, leer un valor que no existe te da null, undefined o un fallo en tiempo de ejecución. En Rust, una lista vacía, un número mal escrito en un campo de texto o un ajuste que falta son casos normales que manejas en el código. Option y Result los hacen visibles, y E0005 se asegura de que no te los saltes sin querer. Mira Patrones en let y Option.
Cómo solucionarlo
Sección titulada «Cómo solucionarlo»Solución 1: let ... else para salir antes
Sección titulada «Solución 1: let ... else para salir antes»Elige esta cuando el resto de la función necesita el valor y, sin él, no hay nada útil que hacer. Es lo que sugiere el help: de rustc. Mantienes tu patrón y añades un bloque else. Ese bloque tiene que salir: return, break, continue o un panic.
fn port_from(text: &str) -> u16 { // saca el número, o sal de la función antes let Ok(port) = text.trim().parse::<u16>() else { println!("'{text}' no es un puerto válido, uso 8080"); return 8080; }; port}
fn main() { let scores = vec![72, 95, 88]; let Some(top) = scores.iter().max() else { println!("Todavía no hay puntuaciones"); return; }; println!("Puntuación máxima: {top}");
println!("Puerto: {}", port_from("3000")); println!("Puerto: {}", port_from("abc"));}Salida:
Puntuación máxima: 95Puerto: 3000'abc' no es un puerto válido, uso 8080Puerto: 8080No dejes el else { todo!() } de rustc tal cual. todo!() provoca un panic cuando se ejecuta, así que cámbialo por una salida de verdad. Más en let-else.
Solución 2: match o if let cuando los dos casos hacen algo
Sección titulada «Solución 2: match o if let cuando los dos casos hacen algo»Elige esta cuando el caso “no coincide” tiene su propio trabajo, como imprimir el error, o cuando el código sigue adelante en los dos casos. match te deja manejar todos los casos y usar el valor del error. También es la forma de desarmar un slice cuando no conoces su longitud. Usa if let cuando solo te importa un caso:
fn main() { // match: maneja los dos resultados y usa el error for input in ["42", "cuarenta y dos"] { match input.parse::<i32>() { Ok(age) => println!("Edad: {age}"), Err(e) => println!("No puedo leer '{input}': {e}"), } }
// un slice puede tener cualquier longitud: haz match sobre su forma for address in ["localhost:8080", "localhost"] { let parts: Vec<&str> = address.split(':').collect(); match parts.as_slice() { [host, port] => println!("host = {host}, puerto = {port}"), _ => println!("'{address}' debería tener la forma host:puerto"), } }
// if let: haz algo solo cuando hay un valor let nickname: Option<&str> = None; if let Some(name) = nickname { println!("¡Hola, {name}!"); } else { println!("¡Hola, forastero!"); }}Salida:
Edad: 42No puedo leer 'cuarenta y dos': invalid digit found in stringhost = localhost, puerto = 8080'localhost' debería tener la forma host:puerto¡Hola, forastero!invalid digit found in string es el mensaje del propio Rust: “se encontró un dígito no válido en la cadena”. El brazo _ atrapa cualquier otra longitud. Sin él, match te daría E0004 con la misma lista de casos “not covered”. Mira if let y match.
Solución 3: Un valor por defecto con unwrap_or, o pasarlo hacia arriba con ?
Sección titulada «Solución 3: Un valor por defecto con unwrap_or, o pasarlo hacia arriba con ?»Elige esta cuando hay un valor por defecto razonable, o cuando quien llama debe ocuparse del problema. unwrap_or(x) te da el valor, o x si no hay ninguno. ? funciona dentro de una función que devuelve Result (u Option): le devuelve el error a quien llamó y detiene la función.
use std::num::ParseIntError;
// ? le pasa el error a quien llama en lugar de manejarlo aquí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() { // un valor por defecto cuando no hay nada let scores: Vec<i32> = Vec::new(); let top = scores.iter().max().copied().unwrap_or(0); println!("Puntuación máxima: {top}");
let volume = "fuerte".parse::<u8>().unwrap_or(50); println!("Volumen: {volume}");
println!("{:?}", total_minutes("2", "30")); println!("{:?}", total_minutes("2", "media"));}Salida:
Puntuación máxima: 0Volumen: 50Ok(150)Err(ParseIntError { kind: InvalidDigit })Si estás seguro de que el valor está ahí (por ejemplo, un número que acabas de escribir en el código), .expect("motivo") también está bien. Provoca un panic con tu mensaje si te equivocaste, así que resérvalo para casos que de verdad no pueden fallar. Usar ? en un main que no devuelve nada da E0277; mira El operador ? y Atajos: unwrap_or.
Solución 4: Usar un tipo cuyo patrón no pueda fallar
Sección titulada «Solución 4: Usar un tipo cuyo patrón no pueda fallar»Elige esta cuando los datos siempre tienen la misma forma, y el patrón solo falla por el tipo que elegiste. Un slice o un Vec pueden tener cualquier longitud, pero un array como [u8; 3] siempre tiene exactamente tres elementos, así que let [r, g, b] = color; siempre coincide. Las tuplas y los structs también coinciden siempre. Y split_once te da las dos mitades en una sola tupla, así que manejas un único Option en lugar de un slice de longitud desconocida:
struct Point { x: i32, y: i32,}
fn main() { // los arrays tienen longitud fija: este patrón siempre coincide let color: [u8; 3] = [255, 140, 0]; let [r, g, b] = color; println!("r={r} g={g} b={b}");
// las tuplas y los structs también coinciden siempre let (host, port) = ("localhost", 8080); let Point { x, y } = Point { x: 3, y: -1 }; println!("{host}:{port} en ({x}, {y})");
// split_once devuelve un solo Option con las dos mitades let Some((key, value)) = "tema=oscuro".split_once('=') else { return; }; println!("{key} -> {value}");}Salida:
r=255 g=140 b=0localhost:8080 en (3, -1)tema -> oscuroLo mismo vale para tus propios enums: let Shape::Circle(radius) = shape; falla con pattern `Shape::Square(_)` not covered. Si el valor de verdad solo puede ser un círculo, quizá debería ser un struct Circle en lugar de un enum. Mira Arrays y Structs.
Dónde lo verás normalmente
Sección titulada «Dónde lo verás normalmente»Un bucle for sobre valores Option. for Some(temp) in readings da “refutable pattern in for loop binding” (patrón refutable en la variable de un bucle for). Solución: for temp in readings.iter().flatten() se salta los None, o recorre todos los elementos y usa if let Some(t) = reading dentro.
Un parámetro de función. fn greet(Some(name): Option<&str>) da “refutable pattern in function argument” (patrón refutable en un argumento de función). Solución: recibe name: Option<&str> y haz match dentro de la función, o recibe name: &str y deja que quien llama decida qué hacer con None.
Leer una entrada. let Ok(n) = line.trim().parse::<i32>(); al leer lo que escribe el usuario o los argumentos de la línea de comandos. Solución: let Ok(n) = ... else { println!("Escribe un número, por favor"); continue; }; dentro del bucle que lee la entrada, o match para imprimir el error.
Errores relacionados
Sección titulada «Errores relacionados»- E0004: non-exhaustive patterns: la misma comprobación de “not covered”, pero para
matchen lugar delet. - E0308: mismatched types: el patrón no encaja en absoluto con el tipo, por ejemplo
let Some(x) = 5;. - E0277: trait bound not satisfied: lo que obtienes al usar
?en una función que no devuelveResultniOption, como unfn main()sin más. - E0282: type annotations needed: suele aparecer junto a
parse(), cuando Rust no sabe qué tipo quieres. Añade::<i32>como en los ejemplos de arriba. - Todos los errores del compilador: la lista completa de páginas de errores.
Comprueba lo que aprendiste
Sección titulada «Comprueba lo que aprendiste»Comprueba lo aprendido
1. ¿Qué let da E0005?
2. ¿Qué tiene que hacer el bloque else de un let ... else?
3. Necesitas un número a partir de un texto, y 0 es un buen valor por defecto si el texto no es un número. ¿Cuál es la solución más corta?
Puntuación: 0 / 3
Para leer más
Sección titulada «Para leer más»- E0005 — Rust error codes index (en inglés): la explicación oficial de este error.
- Refutability — El libro de Rust (en inglés): el capítulo que enlaza rustc, sobre los patrones que pueden fallar y los que no. También existe una traducción al español de la comunidad del libro.
- All the Places Patterns Can Be Used — El libro de Rust (en inglés):
let,if let,while let, los buclesfory los parámetros de funciones. - let-else — Rust by Example (en inglés): ejemplos cortos y ejecutables de
let ... else. - if let — Rust by Example (en inglés): cuándo
if letse lee mejor que unmatchcompleto.