Перейти к содержимому

Ошибка Rust E0004: non-exhaustive patterns not covered (не все случаи match)

E0004 значит, что твой match забыл хотя бы одно возможное значение, а Rust отказывается гадать, что должно происходить в этом случае. Это одна из самых дружелюбных ошибок в Rust: компилятор говорит, какой именно случай пропущен, и даже пишет нужную строку за тебя. Сначала на этой странице показано исправление, а потом объяснено, почему эта проверка — одна из лучших возможностей Rust. Другие ошибки из этой серии собраны на странице Ошибки компилятора Rust.

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!(),
|

Компилятор пишет: образцы не полные, случай OrderStatus::Delivered не покрыт, — и предлагает добавить ветку. Вот программа, которая выдаёт эту ошибку:

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",
// ❌ nothing for Delivered
}
}
fn main() {
println!("{}", describe(OrderStatus::Delivered));
}

Комментарии говорят, что вариант Delivered добавили позже и для него в match ничего нет. Текст между обратными кавычками меняется вместе с твоим кодом. Можно увидеть `None` not covered, `&_` not covered, `0_u8` and `4_u8..=u8::MAX` not covered или `(true, false)` and `(false, true)` not covered. Каждый раз это одна и та же ошибка: Rust перечисляет значения, для которых в твоём match нет ветки.

Читай сообщение сверху вниз:

  1. Строка 8: ^^^^^^ указывает на значение, которое ты сопоставляешь, — status.
  2. «pattern OrderStatus::Delivered not covered»: это случай, который ты забыл.
  3. Примечание (note) переносит тебя к определению перечисления и отмечает забытый вариант в строке 4.
  4. Подсказка (help) показывает, какую ветку добавить. todo!() — это заглушка; замени её настоящим кодом.

Представь торговый автомат с тремя кнопками, из которых подключены только две. Что сделает третья? Rust не даст выпустить автомат с мёртвой кнопкой. У каждой кнопки должен быть ответ, даже если ответ — «не работает».

match в Rust должен быть исчерпывающим (exhaustive): для каждого значения, которое может принять тип, должна подходить ровно одна ветка. Если бы ветки не хватало, программа дошла бы до места, где нечего выполнять. Другие языки в таком случае молча проваливаются дальше, возвращают значение по умолчанию или падают во время работы. Rust ловит это ещё до запуска программы. Основы разобраны в уроке match.

Настоящая польза проявляется потом, когда код растёт. Допустим, ты добавляешь новый вариант в перечисление, которое сопоставляется в десяти местах проекта:

enum OrderStatus {
Pending,
Shipped,
Delivered,
Returned, // new!
}

Теперь компилятор выдаёт по одной E0004 на каждый match, который нужно обновить:

error[E0004]: non-exhaustive patterns: `&OrderStatus::Returned` not covered
--> src/main.rs:9:11
error[E0004]: non-exhaustive patterns: `&OrderStatus::Returned` not covered
--> src/main.rs:17:11

Это готовый список дел. Не нужно обыскивать весь код и надеяться, что ты нашёл каждое место. Вот почему опытные разработчики на Rust так любят перечисления: их можно менять без страха.

Выбирай это для своих перечислений. Чаще всего это правильное исправление, и именно его предлагает help: от rustc. Добавь ветку и замени todo!() тем, что должно происходить на самом деле:

enum OrderStatus {
Pending,
Shipped,
Delivered,
}
fn describe(status: OrderStatus) -> &'static str {
match status {
OrderStatus::Pending => "ждёт упаковки",
OrderStatus::Shipped => "в пути",
OrderStatus::Delivered => "у твоей двери", // ✅ все случаи обработаны
}
}
fn main() {
println!("{}", describe(OrderStatus::Pending));
println!("{}", describe(OrderStatus::Delivered));
}

Вывод:

ждёт упаковки
у твоей двери
▶ Попробуй сам

Если несколько вариантов должны делать одно и то же, объедини их через | в одной ветке: OrderStatus::Shipped | OrderStatus::Delivered => false. Каждый вариант ты всё равно называешь по имени, так что компилятор по-прежнему предупредит о новых. С Option то же самое: если ошибка говорит `None` not covered, добавь ветку None => .... Подробнее в уроке Option.

Исправление 2: диапазоны и _ для чисел и текста

Заголовок раздела «Исправление 2: диапазоны и _ для чисел и текста»

Выбирай это, когда сопоставляешь целые числа, char или строки. У u8 256 возможных значений, а у &str — бесконечно много, так что перечислить их все нельзя. Используй диапазоны вроде 1..=9 для групп чисел и заканчивай _ (или именем вроде other) для «всего остального»:

fn grade(score: u8) -> char {
match score {
90..=100 => 'A',
75..=89 => 'B',
50..=74 => 'C',
0..=49 => 'F',
_ => '?', // от 101 до 255: такой оценки не бывает
}
}
fn run(command: &str) {
match command {
"start" => println!("Запускаю таймер"),
"stop" => println!("Останавливаю таймер"),
other => println!("Неизвестная команда: {other}"),
}
}
fn main() {
println!("{} {} {}", grade(95), grade(62), grade(150));
run("start");
run("pause");
}

Вывод:

A C ?
Запускаю таймер
Неизвестная команда: pause
▶ Попробуй сам

Для строк rustc говорит это прямо: `&str` cannot be matched exhaustively, so a wildcard `_` is necessary (&str нельзя перебрать полностью, поэтому нужен _). Для чисел Rust точно проверяет твои диапазоны. Если они действительно покрывают все значения (например, от 0..=49 до 90..=u8::MAX), _ вообще не нужен. Подробнее в уроке match.

Исправление 3: if let или let else, когда важен один случай

Заголовок раздела «Исправление 3: if let или let else, когда важен один случай»

Выбирай это, когда хочешь действовать только для одного образца, а остальные пропустить. Полный match с пустой веткой «ничего не делать» тоже работает, но if let говорит то же самое понятнее. let else нужен для «достань значение или выйди из функции пораньше»:

fn greet(nickname: Option<&str>) {
if let Some(name) = nickname {
println!("Привет, {name}!");
}
// нет никнейма: ничего не делаем
}
fn first_word_len(text: &str) -> usize {
let Some(word) = text.split_whitespace().next() else {
return 0; // пустой текст: выходим пораньше
};
word.len()
}
fn main() {
greet(Some("Ferris"));
greet(None);
println!("{}", first_word_len("hello world"));
println!("{}", first_word_len(" "));
}

Вывод:

Привет, Ferris!
5
0
▶ Попробуй сам

Строка "hello world" осталась на английском, потому что .len() считает байты, а каждая русская буква занимает в UTF-8 два байта. Блок else в let else обязан покинуть текущий блок (return, break, continue или паника). Подробнее в уроках if let и Сопоставление с образцом.

Исправление 4: ветка _ => для всего остального (с оговоркой)

Заголовок раздела «Исправление 4: ветка _ => для всего остального (с оговоркой)»

Выбирай это, когда большинство вариантов перечисления действительно делают одно и то же. _ подходит подо всё, что не поймали ветки выше:

enum Key {
Up,
Down,
Left,
Right,
Enter,
Escape,
}
fn handle(key: Key) {
match key {
Key::Enter => println!("Подтвердить"),
Key::Escape => println!("Отмена"),
_ => println!("Двигаем курсор"),
}
}
fn main() {
handle(Key::Enter);
handle(Key::Left);
handle(Key::Escape);
}

Вывод:

Подтвердить
Двигаем курсор
Отмена
▶ Попробуй сам

Но честно о цене: _ отключает проверку из раздела Почему Rust так делает. Если потом добавить Key::Delete, компилятор промолчит, и нажатие Delete будет двигать курсор. Это ошибка, которую Rust мог бы поймать за тебя. Для своих перечислений лучше перечислять варианты через | (Key::Up | Key::Down | Key::Left | Key::Right => ...). Оставь _ для чисел, строк и перечислений из чужих крейтов.

Option без None. match nickname { Some(name) => ... } даёт `None` not covered. Исправление: добавь ветку None => или используй if let Some(name) = nickname, если для None делать нечего.

Кортежи и логические значения. У двух bool сразу четыре комбинации, и какие-то легко забыть:

match (logged_in, is_admin) {
(true, true) => println!("Добро пожаловать, админ"),
(false, false) => println!("Пожалуйста, войди"),
} // ❌ patterns `(true, false)` and `(false, true)` not covered

Исправление: добавь недостающие ветки или используй _ внутри кортежа, например (false, _) => println!("Пожалуйста, войди") — это покрывает оба случая «не вошёл». Подробнее в уроке match.

Образец в let. let Some(score) = saved; — это let, который может не совпасть. Это родственная ошибка, E0005, с той же идеей:

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 variant
help: you might want to use `let...else` to handle the variant that isn't matched
|
3 | let Some(score) = saved else { todo!() };
| ++++++++++++++++

Компилятор объясняет: привязке let нужен неопровержимый образец, а случай None не покрыт; он предлагает let...else. Исправление: let Some(score) = saved else { return; }; или if let, как в исправлении 3. Подробнее в уроке Сопоставление с образцом.

Перечисления из других крейтов. Некоторые перечисления, например std::io::ErrorKind, помечены #[non_exhaustive]. Их авторы могут добавить варианты позже, поэтому, даже если ты перечислишь все варианты, которые видишь сегодня, rustc скажет `_` not covered. Исправление: добавь ветку _ =>. Здесь она обязательна, а не просто сокращение.

  • E0005 (refutable pattern in local binding): та же проверка «not covered», но для let, а не для match. Показана выше.
  • E0308: mismatched types, например когда ветки match возвращают разные типы ("text" в одной ветке, число в другой).
  • E0382: borrow of partially moved value — когда ветка вроде Some(name) перемещает String наружу, а потом ты используешь исходное значение. Сопоставляй &nickname.
  • E0507: cannot move out of a borrowed value — когда ветка match пытается забрать String из того, что ты только заимствовал.
  • Все ошибки компилятора: полный список страниц с ошибками.

Проверь себя

1. Ты добавляешь в перечисление вариант Refunded. Что компилятор сделает с твоими выражениями match, где каждый вариант назван по имени?

2. Ты сопоставляешь &str с "yes" и "no". Как исправить E0004?

3. Чем плох _ => в match по твоему собственному перечислению?

Результат: 0 / 3