Ошибка Rust E0005: refutable pattern in local binding (опровержимый образец в let)
E0005 значит, что ты написал let с образцом, который может не совпасть, например let Some(x) = value;, когда value может оказаться None. Rust нужно знать, что делать в этом случае, а у простого let для этого нет места. Исправление — дать случаю «не совпало» куда-то пойти: обычно это let ... else, if let или match. Это очень частая ошибка, когда только начинаешь работать с Option и Result, и исправляется она за минуту. Остальные ошибки из этой серии ищи на странице Ошибки компилятора Rust.
Сообщение об ошибке
Заголовок раздела «Сообщение об ошибке»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? | ++++++++++++++++Компилятор говорит: образец в let опровержимый, вариант None не покрыт, и предлагает let...else. Вот программа, которая выдаёт эту ошибку (комментарий «what if the list was empty?» значит «а если список пустой?»):
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}");}Та же ошибка бывает и с другими образцами. Меняется только часть «not covered» (не покрыт):
3 | let Ok(age) = input.parse::<i32>(); | ^^^^^^^ pattern `Err(_)` not covered
3 | let [host, port] = parts.as_slice(); | ^^^^^^^^^^^^ patterns `&[]`, `&[_]` and `&[_, _, _, ..]` not coveredЧто это значит
Заголовок раздела «Что это значит»Здесь всё решают два слова:
- Опровержимый (refutable) образец может не совпасть.
Some(top)не совпадает, когда значение —None.Ok(age)не совпадает сErr.[host, port]не совпадает, если в срезе не ровно два элемента. - Неопровержимый (irrefutable) образец совпадает всегда. Простое имя, как в
let top = best;, работает всегда, как и кортежи и структуры:let (x, y) = point;.
let обязан срабатывать всегда, потому что следующая строка пользуется созданными им переменными. Если бы best было None, чем было бы top на следующей строке? Ответа нет, поэтому Rust тебя останавливает. Пометка «not covered» перечисляет ровно те случаи, которые ты забыл: None, Err(_) или, для среза, «пусто», «один элемент» и «три элемента и больше».
Представь постамат. let Some(top) = best; — это как уйти, будучи уверенным, что посылка внутри, даже не открыв дверцу. Rust заставляет решить, что делать, если ячейка пустая.
Почему Rust так делает
Заголовок раздела «Почему Rust так делает»let, параметры функций и циклы for требуют образцов, которые не могут не совпасть. match, if let, while let и let ... else принимают образцы, которые могут не совпасть, потому что у каждой из этих конструкций есть место для другого случая. Это то же правило, что и в E0004, где match должен покрывать все значения. Здесь «match» — это let всего с одной веткой.
Во многих языках чтение несуществующего значения даёт null, undefined или падение во время выполнения. В Rust пустой список, неправильное число в текстовом поле или отсутствующая настройка — обычные случаи, которые ты обрабатываешь в коде. Option и Result делают их заметными, а E0005 следит, чтобы ты не пропустил их случайно. Подробнее — в разделе Образцы в let и параметрах функций и в уроке Option.
Как исправить
Заголовок раздела «Как исправить»Исправление 1: let ... else, чтобы выйти пораньше
Заголовок раздела «Исправление 1: let ... else, чтобы выйти пораньше»Выбирай этот вариант, когда остальной функции нужно значение, а без него делать нечего. Именно это советует help: от rustc. Ты оставляешь свой образец и добавляешь блок else. Этот блок обязан выйти: return, break, continue или паника.
fn port_from(text: &str) -> u16 { // достаём число или выходим из функции пораньше let Ok(port) = text.trim().parse::<u16>() else { println!("'{text}' — неправильный порт, берём 8080"); return 8080; }; port}
fn main() { let scores = vec![72, 95, 88]; let Some(top) = scores.iter().max() else { println!("Результатов пока нет"); return; }; println!("Лучший результат: {top}");
println!("Порт: {}", port_from("3000")); println!("Порт: {}", port_from("abc"));}Вывод:
Лучший результат: 95Порт: 3000'abc' — неправильный порт, берём 8080Порт: 8080Не оставляй else { todo!() } из подсказки rustc как есть. todo!() паникует, когда выполняется, поэтому замени его настоящим выходом. Подробнее — в разделе matches! и let-else.
Исправление 2: match или if let, когда оба случая что-то делают
Заголовок раздела «Исправление 2: match или if let, когда оба случая что-то делают»Выбирай этот вариант, когда у случая «не совпало» есть своя работа, например вывести ошибку, или когда код продолжается в любом случае. match позволяет обработать каждый случай и воспользоваться значением ошибки. Так же разбирают срез, длину которого ты не знаешь заранее. if let подходит, когда важен только один случай:
fn main() { // match: обрабатываем оба исхода и используем ошибку for input in ["42", "сорок два"] { match input.parse::<i32>() { Ok(age) => println!("Возраст: {age}"), Err(e) => println!("Не получается прочитать '{input}': {e}"), } }
// у среза может быть любая длина: сопоставляем по форме for address in ["localhost:8080", "localhost"] { let parts: Vec<&str> = address.split(':').collect(); match parts.as_slice() { [host, port] => println!("хост = {host}, порт = {port}"), _ => println!("'{address}' должен выглядеть как host:port"), } }
// if let: делаем что-то, только когда значение есть let nickname: Option<&str> = None; if let Some(name) = nickname { println!("Привет, {name}!"); } else { println!("Привет, незнакомец!"); }}Вывод:
Возраст: 42Не получается прочитать 'сорок два': invalid digit found in stringхост = localhost, порт = 8080'localhost' должен выглядеть как host:portПривет, незнакомец!Текст invalid digit found in string («в строке найден недопустимый символ») — это сообщение самого Rust, поэтому оно на английском. Ветка _ ловит все остальные длины. Без неё match выдал бы E0004 с тем же списком «not covered». Смотри уроки if let и match.
Исправление 3: значение по умолчанию через unwrap_or или передача дальше через ?
Заголовок раздела «Исправление 3: значение по умолчанию через unwrap_or или передача дальше через ?»Выбирай этот вариант, когда есть разумное значение по умолчанию или когда разбираться с проблемой должен вызывающий код. unwrap_or(x) даёт значение, а если его нет — x. ? работает внутри функции, которая возвращает Result (или Option): он отдаёт ошибку вызывающему коду и завершает функцию.
use std::num::ParseIntError;
// ? передаёт ошибку вызывающему коду, а не обрабатывает её здесь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() { // значение по умолчанию, когда ничего нет let scores: Vec<i32> = Vec::new(); let top = scores.iter().max().copied().unwrap_or(0); println!("Лучший результат: {top}");
let volume = "громко".parse::<u8>().unwrap_or(50); println!("Громкость: {volume}");
println!("{:?}", total_minutes("2", "30")); println!("{:?}", total_minutes("2", "половина"));}Вывод:
Лучший результат: 0Громкость: 50Ok(150)Err(ParseIntError { kind: InvalidDigit })Если ты уверен, что значение есть (например, это число, которое ты сам только что написал в коде), подойдёт и .expect("причина"). Если ты ошибся, программа запаникует с твоим сообщением, так что оставляй его для случаев, которые правда не могут сломаться. ? в main, который ничего не возвращает, даёт E0277; подробнее — в уроках Result и оператор ? и Option.
Исправление 4: возьми тип, образец которого не может не совпасть
Заголовок раздела «Исправление 4: возьми тип, образец которого не может не совпасть»Выбирай этот вариант, когда данные всегда одной формы, а образец не совпадает только из-за выбранного типа. У среза или Vec может быть любая длина, а у массива вроде [u8; 3] всегда ровно три элемента, поэтому let [r, g, b] = color; совпадает всегда. Кортежи и структуры тоже совпадают всегда. А split_once отдаёт обе половины одним кортежем, так что ты имеешь дело с одним Option, а не со срезом неизвестной длины:
struct Point { x: i32, y: i32,}
fn main() { // у массива фиксированная длина: этот образец совпадает всегда let color: [u8; 3] = [255, 140, 0]; let [r, g, b] = color; println!("r={r} g={g} b={b}");
// кортежи и структуры тоже совпадают всегда let (host, port) = ("localhost", 8080); let Point { x, y } = Point { x: 3, y: -1 }; println!("{host}:{port} в точке ({x}, {y})");
// split_once возвращает один Option с обеими половинами let Some((key, value)) = "theme=dark".split_once('=') else { return; }; println!("{key} -> {value}");}Вывод:
r=255 g=140 b=0localhost:8080 в точке (3, -1)theme -> darkС твоими собственными перечислениями так же: let Shape::Circle(radius) = shape; падает с pattern `Shape::Square(_)` not covered. Если значение и правда может быть только кругом, возможно, ему лучше быть структурой Circle, а не перечислением. Смотри уроки Массивы и Структуры.
Где это обычно встречается
Заголовок раздела «Где это обычно встречается»Цикл for по значениям Option. for Some(temp) in readings даёт «refutable pattern in for loop binding». Исправление: for temp in readings.iter().flatten() пропускает все None, или перебирай все элементы и пиши внутри if let Some(t) = reading.
Параметр функции. fn greet(Some(name): Option<&str>) даёт «refutable pattern in function argument». Исправление: принимай name: Option<&str> и делай match внутри функции или принимай name: &str и пусть вызывающий код решает, что делать с None.
Разбор ввода. let Ok(n) = line.trim().parse::<i32>(); при чтении пользовательского ввода или аргументов командной строки. Исправление: let Ok(n) = ... else { println!("Введи число"); continue; }; внутри цикла ввода или match, чтобы вывести ошибку.
Связанные ошибки
Заголовок раздела «Связанные ошибки»- E0004: non-exhaustive patterns: та же проверка «not covered», только для
match, а не дляlet. - E0308: mismatched types: образец вообще не подходит к типу, например
let Some(x) = 5;. - E0277: trait bound not satisfied: получишь её, если используешь
?в функции, которая не возвращаетResultилиOption, например в обычномfn main(). - E0282: type annotations needed: часто появляется рядом с
parse(), когда Rust не может понять, какой тип тебе нужен. Добавь::<i32>, как в примерах выше. - Все ошибки компилятора: полный список страниц об ошибках.
Проверь себя
Заголовок раздела «Проверь себя»Проверь себя
1. Какой let даёт E0005?
2. Что обязан сделать блок else в let ... else?
3. Тебе нужно число из текста, и 0 вполне подходит по умолчанию, если в тексте не число. Какое исправление самое короткое?
Результат: 0 / 3
Что почитать
Заголовок раздела «Что почитать»- E0005 — Rust error codes index (на английском): официальное объяснение этой ошибки.
- Опровержимость: может ли шаблон не соответствовать — книга «Язык программирования Rust» (перевод на русский): глава, на которую ссылается rustc, — об образцах, которые могут и не могут не совпасть.
- Все места, где могут использоваться шаблоны — книга «Язык программирования Rust» (перевод на русский):
let,if let,while let, циклыforи параметры функций. - let-else — Rust by Example (на английском): короткие запускаемые примеры
let ... else. - if let — Rust на примерах (перевод на русский): когда
if letчитается лучше, чем полныйmatch.