Rust 에러 E0005: refutable pattern in local binding (실패할 수 있는 let 패턴)
E0005는 value가 None일 수도 있는데 let Some(x) = value;라고 쓴 것처럼, 매칭에 실패할 수도 있는 패턴을 let에 썼다는 뜻이에요. 그 경우에 무슨 일이 일어나야 하는지 Rust가 알아야 하는데, 평범한 let에는 그걸 적을 자리가 없어요. 해결책은 “매칭되지 않은 경우”가 갈 곳을 만들어 주는 거예요. 보통은 let ... else, if let, match 중 하나를 써요. Option과 Result를 쓰기 시작하면 아주 자주 만나는 에러지만, 고치는 데는 1분이면 충분해요. 이 시리즈의 다른 에러는 Rust 컴파일러 에러 해설에서 볼 수 있어요.
에러 메시지
섹션 제목: “에러 메시지”error[E0005]: refutable pattern in local binding --> src/main.rs:4:9 |4 | let Some(top) = best; // ❌ 목록이 비어 있었다면요? | ^^^^^^^^^ 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!() }; // ❌ 목록이 비어 있었다면요? | ++++++++++++++++“refutable pattern in local binding”은 “지역 바인딩에 반박 가능한(실패할 수 있는) 패턴을 썼다”는 뜻이고, 라벨은 “None 패턴을 처리하지 않았다”고 알려 줘요. 첫 번째 note는 “let 바인딩에는 구조체나 배리언트가 하나뿐인 열거형처럼 반박 불가능한 패턴이 필요하다”고 하고, 세 번째 note는 매칭하는 값의 타입이 Option<i32>라고 알려 줘요. help는 “매칭되지 않는 배리언트는 let...else로 처리해 보라”고 제안해요.
이 에러를 내는 프로그램은 다음과 같아요.
fn main() { let scores = vec![72, 95, 88]; let best = scores.iter().max().copied(); let Some(top) = best; // ❌ 목록이 비어 있었다면요? 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은 언제나 성공해야 해요. 바로 다음 줄에서 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는 실패할 수 있는 패턴도 받아요. 다른 경우를 처리할 자리가 저마다 있기 때문이에요. match가 모든 값을 처리해야 한다는 E0004와 같은 규칙이에요. 여기서는 갈래가 하나뿐인 let이 “match”인 셈이죠.
많은 언어에서는 없는 값을 읽으면 null이나 undefined가 나오거나, 실행 중에 프로그램이 멈춰요. Rust에서는 빈 목록, 입력란의 잘못된 숫자, 빠진 설정값이 모두 코드에서 처리하는 평범한 경우예요. Option과 Result가 그런 경우를 눈에 보이게 해 주고, E0005는 실수로 건너뛰지 않게 지켜 줘요. let과 함수 매개변수에서의 패턴과 Option을 참고하세요.
고치는 방법
섹션 제목: “고치는 방법”방법 1: let ... else로 일찍 빠져나가기
섹션 제목: “방법 1: let ... else로 일찍 빠져나가기”함수의 나머지 부분에 그 값이 꼭 필요하고, 값이 없으면 할 수 있는 일이 없을 때 이 방법을 고르세요. rustc의 help:가 제안하는 방법이에요. 패턴은 그대로 두고 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을 써요포트: 8080rustc가 제안한 else { todo!() }를 그대로 두지는 마세요. todo!()는 실행되면 패닉을 일으키니까, 진짜 빠져나갈 방법으로 바꾸세요. 더 자세한 내용은 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 = {host}, port = {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 stringhost = localhost, port = 8080'localhost' 주소는 host:port 모양이어야 해요안녕하세요, 손님!invalid digit found in string은 Rust 표준 라이브러리가 출력하는 에러 메시지로, “문자열에 숫자가 아닌 글자가 있다”는 뜻이에요. _ 갈래는 나머지 모든 길이를 받아 줘요. 이 갈래가 없으면 match가 같은 “not covered” 목록과 함께 E0004를 내요. 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 = "loud".parse::<u8>().unwrap_or(50); println!("볼륨: {volume}");
println!("{:?}", total_minutes("2", "30")); println!("{:?}", total_minutes("2", "half"));}실행 결과:
최고 점수: 0볼륨: 50Ok(150)Err(ParseIntError { kind: InvalidDigit })마지막 줄의 Err(ParseIntError { kind: InvalidDigit })는 "half"를 숫자로 읽지 못해서 생긴 에러를 디버그 형식으로 출력한 거예요.
값이 확실히 있다면(예를 들어 코드에 직접 쓴 숫자라면) .expect("이유")도 괜찮아요. 틀렸을 때는 내가 쓴 메시지와 함께 패닉을 일으키니까, 정말 실패할 수 없는 경우에만 쓰세요. 아무것도 반환하지 않는 main에서 ?를 쓰면 E0277이 나와요. ? 연산자와 지름길: unwrap_or와 unwrap을 참고하세요.
방법 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(Shape::Square(_) 패턴을 처리하지 않음)와 함께 실패해요. 값이 정말 원일 수밖에 없다면, 열거형 대신 Circle 구조체를 쓰는 게 맞을지도 몰라요. 배열과 구조체를 참고하세요.
자주 보게 되는 곳
섹션 제목: “자주 보게 되는 곳”Option을 도는 for 반복문. for Some(temp) in readings는 “refutable pattern in for loop binding”(for 반복문 바인딩에 반박 가능한 패턴)을 내요. 해결: 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” 검사를
let대신match에 하는 에러예요. - E0308: mismatched types: 패턴이 타입과 아예 맞지 않아요. 예를 들어
let Some(x) = 5;예요. - E0277: trait bound not satisfied: 평범한
fn main()처럼Result나Option을 반환하지 않는 함수에서?를 쓰면 나와요. - E0282: type annotations needed: Rust가 원하는 타입을 알 수 없을 때
parse()근처에서 자주 나와요. 위의 예제처럼::<i32>를 붙이세요. - 전체 컴파일러 에러: 에러 페이지 전체 목록이에요.
이해도 점검
섹션 제목: “이해도 점검”이해도 점검
1. E0005를 내는 let은 무엇일까요?
2. let ... else의 else 블록은 무엇을 해야 할까요?
3. 텍스트에서 숫자를 얻어야 하고, 숫자가 아니면 0이 기본값이어도 괜찮아요. 가장 짧은 해결책은 무엇일까요?
점수: 0 / 3
더 알아보기
섹션 제목: “더 알아보기”- E0005 — Rust error codes index (영어): 이 에러의 공식 설명이에요.
- 반박 가능성 — The Rust Programming Language 한국어판: rustc가 링크하는 장의 한국어판이에요. 실패할 수 있는 패턴과 실패할 수 없는 패턴을 다뤄요.
- 패턴이 사용될 수 있는 모든 곳 — The Rust Programming Language 한국어판:
let,if let,while let,for반복문, 함수 매개변수의 패턴을 다뤄요. - let-else — Rust by Example (영어):
let ... else를 보여 주는 짧은 실행 예제예요. - if let — Rust by Example (영어): 전체
match보다if let이 더 읽기 좋은 경우를 보여 줘요.