Rust Error E0005: Refutable Pattern in Local Binding
E0005 means you wrote a let whose pattern might not match, like let Some(x) = value; when value could be None. Rust needs to know what happens in that case, and a plain let has no place to say it. The fix is to give the “didn’t match” case somewhere to go: usually let ... else, if let, or match. This is a very common error when you start working with Option and Result, and it takes a minute to fix. For the other errors in this series, see Rust compiler errors.
The error message
Section titled “The error message”error[E0005]: refutable pattern in local binding --> src/main.rs:4:9 |4 | let Some(top) = best; // ❌ what if the list was empty? | ^^^^^^^^^ 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!() }; // ❌ what if the list was empty? | ++++++++++++++++This is the program that produces it:
fn main() { let scores = vec![72, 95, 88]; let best = scores.iter().max().copied(); let Some(top) = best; // ❌ what if the list was empty? println!("Top score: {top}");}You’ll see the same error with other patterns. Only the “not covered” part changes:
3 | let Ok(age) = input.parse::<i32>(); | ^^^^^^^ pattern `Err(_)` not covered
3 | let [host, port] = parts.as_slice(); | ^^^^^^^^^^^^ patterns `&[]`, `&[_]` and `&[_, _, _, ..]` not coveredWhat it means
Section titled “What it means”Two words do the work here:
- A refutable pattern can fail to match.
Some(top)fails when the value isNone.Ok(age)fails onErr.[host, port]fails when the slice doesn’t have exactly two items. - An irrefutable pattern always matches. A plain name like
let top = best;always works, and so do tuples and structs likelet (x, y) = point;.
A let must always succeed, because the next line uses the variables it creates. If best were None, what would top be on the next line? There’s no answer, so Rust stops you. The “not covered” label lists exactly the cases you forgot: None, Err(_), or for the slice, “empty”, “one item” and “three or more items”.
Think of a parcel locker. let Some(top) = best; is like walking away sure that a parcel is inside, without opening the door. Rust makes you decide what to do if the locker is empty.
Why Rust does this
Section titled “Why Rust does this”let, function parameters and for loops all need patterns that can’t fail. match, if let, while let and let ... else accept patterns that can fail, because each of them has a place for the other case. This is the same rule as E0004, where a match must cover every value. Here the “match” is a let with just one arm.
In many languages, reading a value that isn’t there gives you null, undefined or a crash at runtime. In Rust, an empty list, a bad number in a text field or a missing setting are normal cases you handle in code. Option and Result make them visible, and E0005 makes sure you don’t skip them by accident. See Patterns in let and Option.
How to fix it
Section titled “How to fix it”Fix 1: let ... else to leave early
Section titled “Fix 1: let ... else to leave early”Choose this when the rest of the function needs the value, and without it there’s nothing useful to do. It’s what rustc’s help: suggests. You keep your pattern and add an else block. That block must leave: return, break, continue or a panic.
fn port_from(text: &str) -> u16 { // get the number out, or leave the function early let Ok(port) = text.trim().parse::<u16>() else { println!("'{text}' is not a valid port, using 8080"); return 8080; }; port}
fn main() { let scores = vec![72, 95, 88]; let Some(top) = scores.iter().max() else { println!("No scores yet"); return; }; println!("Top score: {top}");
println!("Port: {}", port_from("3000")); println!("Port: {}", port_from("abc"));}Output:
Top score: 95Port: 3000'abc' is not a valid port, using 8080Port: 8080Don’t keep rustc’s else { todo!() } as it is. todo!() panics when it runs, so replace it with a real way out. More in let-else.
Fix 2: match or if let when both cases do something
Section titled “Fix 2: match or if let when both cases do something”Choose this when the “no match” case has its own work to do, like printing the error, or when the code continues either way. match lets you handle every case and use the error value. It’s also the way to take a slice apart when you don’t know its length. Use if let when only one case matters:
fn main() { // match: handle both outcomes, and use the error for input in ["42", "forty-two"] { match input.parse::<i32>() { Ok(age) => println!("Age: {age}"), Err(e) => println!("Can't read '{input}': {e}"), } }
// a slice can have any length: match on its shape for address in ["localhost:8080", "localhost"] { let parts: Vec<&str> = address.split(':').collect(); match parts.as_slice() { [host, port] => println!("host = {host}, port = {port}"), _ => println!("'{address}' should look like host:port"), } }
// if let: only do something when there is a value let nickname: Option<&str> = None; if let Some(name) = nickname { println!("Hi, {name}!"); } else { println!("Hi, stranger!"); }}Output:
Age: 42Can't read 'forty-two': invalid digit found in stringhost = localhost, port = 8080'localhost' should look like host:portHi, stranger!The _ arm catches every other length. Without it, match would give you E0004 with the same “not covered” list. See if let and match.
Fix 3: A default with unwrap_or, or pass it on with ?
Section titled “Fix 3: A default with unwrap_or, or pass it on with ?”Choose this when there’s a sensible default value, or when the caller should deal with the problem. unwrap_or(x) gives you the value, or x if there is none. ? works inside a function that returns Result (or Option): it hands the error back to the caller and stops the function.
use std::num::ParseIntError;
// ? passes the error to the caller instead of handling it herefn 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() { // a default value when there is nothing let scores: Vec<i32> = Vec::new(); let top = scores.iter().max().copied().unwrap_or(0); println!("Top score: {top}");
let volume = "loud".parse::<u8>().unwrap_or(50); println!("Volume: {volume}");
println!("{:?}", total_minutes("2", "30")); println!("{:?}", total_minutes("2", "half"));}Output:
Top score: 0Volume: 50Ok(150)Err(ParseIntError { kind: InvalidDigit })If you’re sure the value is there (for example a number you just wrote in the code), .expect("reason") is fine too. It panics with your message if you were wrong, so keep it for cases that really can’t fail. Using ? in a main that returns nothing gives E0277; see The ? operator and Shortcuts: unwrap_or.
Fix 4: Use a type whose pattern can’t fail
Section titled “Fix 4: Use a type whose pattern can’t fail”Choose this when the data always has the same shape, and the pattern only fails because of the type you picked. A slice or Vec can have any length, but an array like [u8; 3] always has exactly three items, so let [r, g, b] = color; always matches. Tuples and structs always match too. And split_once gives you both halves as one tuple, so you deal with one Option instead of a slice of unknown length:
struct Point { x: i32, y: i32,}
fn main() { // arrays have a fixed length: this pattern always matches let color: [u8; 3] = [255, 140, 0]; let [r, g, b] = color; println!("r={r} g={g} b={b}");
// tuples and structs always match too let (host, port) = ("localhost", 8080); let Point { x, y } = Point { x: 3, y: -1 }; println!("{host}:{port} at ({x}, {y})");
// split_once returns one Option with both halves let Some((key, value)) = "theme=dark".split_once('=') else { return; }; println!("{key} -> {value}");}Output:
r=255 g=140 b=0localhost:8080 at (3, -1)theme -> darkThe same goes for your own enums: let Shape::Circle(radius) = shape; fails with pattern `Shape::Square(_)` not covered. If the value can really only be a circle, maybe it should be a Circle struct instead of an enum. See Arrays and Structs.
Where you’ll usually see it
Section titled “Where you’ll usually see it”A for loop over Options. for Some(temp) in readings gives “refutable pattern in for loop binding”. Fix: for temp in readings.iter().flatten() skips the Nones, or loop over every item and use if let Some(t) = reading inside.
A function parameter. fn greet(Some(name): Option<&str>) gives “refutable pattern in function argument”. Fix: take name: Option<&str> and match inside the function, or take name: &str and let the caller decide what to do with None.
Parsing input. let Ok(n) = line.trim().parse::<i32>(); when reading user input or command-line arguments. Fix: let Ok(n) = ... else { println!("Please type a number"); continue; }; inside the input loop, or match to print the error.
Related errors
Section titled “Related errors”- E0004: non-exhaustive patterns: the same “not covered” check, for
matchinstead oflet. - E0308: mismatched types: the pattern doesn’t fit the type at all, for example
let Some(x) = 5;. - E0277: trait bound not satisfied: what you get when you use
?in a function that doesn’t returnResultorOption, such as a plainfn main(). - E0282: type annotations needed: often shows up next to
parse(), when Rust can’t tell what type you want. Add::<i32>as in the examples above. - All compiler errors: the full list of error pages.
Quick check
Section titled “Quick check”Quick check
1. Which let gives E0005?
2. What must the else block of a let ... else do?
3. You need a number from text, and 0 is a fine default if the text is not a number. What is the shortest fix?
Score: 0 / 3
Read more
Section titled “Read more”- E0005 — Rust error codes index: the official explanation of this error.
- Refutability — The Rust Book: the chapter rustc links to, on patterns that can and can’t fail.
- All the Places Patterns Can Be Used — The Rust Book:
let,if let,while let,forloops and function parameters. - let-else — Rust by Example: short runnable examples of
let ... else. - if let — Rust by Example: when
if letreads better than a fullmatch.