Ошибка 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 нет ветки.
Что это значит
Заголовок раздела «Что это значит»Читай сообщение сверху вниз:
- Строка 8:
^^^^^^указывает на значение, которое ты сопоставляешь, —status. - «pattern
OrderStatus::Deliverednot covered»: это случай, который ты забыл. - Примечание (note) переносит тебя к определению перечисления и отмечает забытый вариант в строке 4.
- Подсказка (help) показывает, какую ветку добавить.
todo!()— это заглушка; замени её настоящим кодом.
Представь торговый автомат с тремя кнопками, из которых подключены только две. Что сделает третья? Rust не даст выпустить автомат с мёртвой кнопкой. У каждой кнопки должен быть ответ, даже если ответ — «не работает».
Почему Rust так делает
Заголовок раздела «Почему 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:11error[E0004]: non-exhaustive patterns: `&OrderStatus::Returned` not covered --> src/main.rs:17:11Это готовый список дел. Не нужно обыскивать весь код и надеяться, что ты нашёл каждое место. Вот почему опытные разработчики на Rust так любят перечисления: их можно менять без страха.
Как исправить
Заголовок раздела «Как исправить»Исправление 1: добавь недостающую ветку
Заголовок раздела «Исправление 1: добавь недостающую ветку»Выбирай это для своих перечислений. Чаще всего это правильное исправление, и именно его предлагает 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!50Строка "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 varianthelp: 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
Что почитать
Заголовок раздела «Что почитать»- E0004 — список кодов ошибок Rust (на английском): официальное объяснение этой ошибки.
- E0005 — список кодов ошибок Rust (на английском): родственная ошибка для образцов в
let. - Конструкция потока управления match — книга «Язык программирования Rust» (перевод на русский): глава, которая объясняет, почему
matchисчерпывающий, и рассказывает о заполнителе_. - Опровержимость — книга «Язык программирования Rust» (перевод на русский): когда образец может не совпасть и где это допустимо.
- match — Rust на примерах (перевод на русский): короткие запускаемые примеры с диапазонами,
|и_в ветках.