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, // 나중에 추가했어요 | --------- 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!(), |“non-exhaustive patterns: OrderStatus::Delivered not covered”는 “패턴이 모든 경우를 다루지 않는다, OrderStatus::Delivered가 빠졌다”는 뜻이에요. help는 “와일드카드 패턴이나, 아래처럼 명시적인 패턴으로 match 갈래(arm)를 추가해서 가능한 모든 경우를 처리하라”고 제안해요.
이 에러를 내는 프로그램은 다음과 같아요.
enum OrderStatus { Pending, Shipped, Delivered, // 나중에 추가했어요}
fn describe(status: OrderStatus) -> &'static str { match status { OrderStatus::Pending => "waiting to be packed", OrderStatus::Shipped => "on its way", // ❌ Delivered를 처리하는 갈래가 없어요 }}
fn main() { println!("{}", describe(OrderStatus::Delivered));}백틱 사이의 글자는 코드에 따라 달라져요. `None` not covered, `&_` not covered, `0_u8` and `4_u8..=u8::MAX` not covered, `(true, false)` and `(false, true)` not covered 같은 문구를 볼 수도 있어요. 모두 같은 에러예요. match에 갈래가 없는 값들을 Rust가 나열해 주는 거예요.
무슨 뜻인가요
섹션 제목: “무슨 뜻인가요”메시지를 위에서 아래로 읽어 보세요.
- 8번째 줄:
^^^^^^는 매칭하는 값인status를 가리켜요. - “pattern
OrderStatus::Deliverednot covered”: 이게 빠뜨린 경우예요. - note는 열거형 정의로 건너가서, 4번째 줄의 빠진 배리언트(variant)를 표시해요.
- help는 추가할 갈래를 정확히 보여 줘요.
todo!()는 임시로 넣어 둔 자리예요. 진짜 코드로 바꾸면 돼요.
버튼이 세 개인 자판기를 떠올려 보세요. 그중 두 개만 연결되어 있어요. 세 번째 버튼을 누르면… 무슨 일이 일어나죠? Rust는 먹통인 버튼이 있는 자판기를 내보내지 않아요. 답이 “고장”일지라도, 모든 버튼에는 답이 있어야 해요.
Rust가 이렇게 하는 이유
섹션 제목: “Rust가 이렇게 하는 이유”Rust의 match는 빠짐없어야(exhaustive) 해요. 그 타입이 가질 수 있는 모든 값에 대해, 딱 맞는 갈래가 있어야 한다는 뜻이에요. 갈래가 하나라도 빠지면 프로그램이 실행할 코드가 없는 지점에 도착하게 돼요. 다른 언어는 조용히 다음으로 넘어가거나, 기본값을 돌려주거나, 실행 중에 죽어 버려요. Rust는 프로그램이 실행되기도 전에 잡아내요. 기본은 match 강좌에서 다뤄요.
진짜 이득은 코드가 커진 나중에 와요. 프로젝트 곳곳의 열 군데에서 매칭하는 열거형에 새 배리언트를 추가한다고 해 볼게요.
enum OrderStatus { Pending, Shipped, Delivered, Returned, // 새로 추가!}그러면 컴파일러가 고쳐야 할 match마다 E0004를 하나씩 알려 줘요.
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: 빠진 갈래 추가하기”내가 만든 열거형이라면 이 방법을 고르세요. 대부분은 이게 정답이고, rustc의 help:가 제안하는 것도 바로 이거예요. 갈래를 추가하고, todo!()를 실제로 일어나야 할 일로 바꾸세요.
enum OrderStatus { Pending, Shipped, Delivered,}
fn describe(status: OrderStatus) -> &'static str { match status { OrderStatus::Pending => "waiting to be packed", OrderStatus::Shipped => "on its way", OrderStatus::Delivered => "at your door", // ✅ 모든 경우를 처리했어요 }}
fn main() { println!("{}", describe(OrderStatus::Pending)); println!("{}", describe(OrderStatus::Delivered));}실행 결과:
waiting to be packedat your door여러 배리언트가 같은 일을 해야 한다면 |로 묶어서 갈래 하나로 쓰세요. 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까지) _는 전혀 필요 없어요. 자세한 내용은 범위 매칭하기에 있어요.
방법 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!50let else의 else 블록은 반드시 현재 블록을 빠져나가야 해요(return, break, continue, 또는 패닉). if let과 let-else를 참고하세요.
방법 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 => ...). _는 숫자, 문자열, 다른 크레이트의 열거형에 쓰세요.
자주 보게 되는 곳
섹션 제목: “자주 보게 되는 곳”None이 빠진 Option. match nickname { Some(name) => ... }는 `None` not covered를 내요. 해결: None => 갈래를 추가하거나, None일 때 할 일이 없다면 if let Some(name) = nickname을 쓰세요.
튜플과 불리언. bool 두 개를 한꺼번에 매칭하면 조합이 네 가지라서, 몇 개를 빠뜨리기 쉬워요.
match (logged_in, is_admin) { (true, true) => println!("Welcome, admin"), (false, false) => println!("Please log in"),} // ❌ patterns `(true, false)` and `(false, true)` not covered해결: 빠진 갈래를 추가하거나, 튜플 안에 _를 쓰세요. (false, _) => println!("Please log in")처럼 쓰면 “로그아웃 상태”인 두 경우를 모두 덮어요. 여러 값을 한 번에 매칭하기를 참고하세요.
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!() }; | ++++++++++++++++“refutable pattern in local binding”은 “지역 바인딩에 반박 가능한(실패할 수 있는) 패턴을 썼다”는 뜻이에요. note는 “let에는 구조체나 배리언트가 하나뿐인 열거형처럼 반박 불가능한 패턴이 필요하다”고 하고, help는 “매칭되지 않는 배리언트는 let...else로 처리해 보라”고 제안해요. 해결: let Some(score) = saved else { return; };라고 쓰거나, 방법 3처럼 if let을 쓰세요. let과 함수 매개변수에서의 패턴을 참고하세요.
다른 크레이트의 열거형. std::io::ErrorKind 같은 일부 열거형에는 #[non_exhaustive]가 붙어 있어요. 만든 사람이 나중에 배리언트를 추가할 수 있다는 뜻이에요. 그래서 지금 보이는 배리언트를 모두 나열해도 rustc는 `_` not covered라고 해요. 해결: _ => 갈래를 추가하세요. 이 경우엔 지름길이 아니라 꼭 필요한 거예요.
관련 에러
섹션 제목: “관련 에러”- E0005(refutable pattern in local binding): 똑같은 “not covered” 검사를
match대신let에 하는 에러예요. 위에서 봤어요. - E0308: 타입이 맞지 않아요. 예를 들어 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. 내가 만든 열거형을 매칭할 때 _ =>를 쓰면 어떤 단점이 있을까요?
점수: 0 / 3
더 알아보기
섹션 제목: “더 알아보기”- E0004 — Rust error codes index (영어): 이 에러의 공식 설명이에요.
- E0005 — Rust error codes index (영어):
let의 패턴에 대한 자매 에러예요. - match 제어 흐름 구조 — The Rust Programming Language 한국어판: match가 왜 빠짐없어야 하는지, 그리고
_자리표시자를 설명하는 장이에요. - 반박 가능성 — The Rust Programming Language 한국어판: 패턴이 매칭에 실패할 수 있는 경우와, 그런 패턴을 쓸 수 있는 곳을 다뤄요.
- match — Rust by Example (영어): match 갈래에서 범위,
|,_를 쓰는 짧은 실행 예제예요.