반박 가능성: 패턴이 매칭에 실패할 수 있는가
패턴은 두 종류로 나뉩니다. 반박 가능한 패턴과 반박 불가능한 패턴입니다. 어떤 값이
들어오든 항상 매칭되는 패턴을 반박 불가능한(irrefutable) 패턴이라고 합니다.
예를 들어 let x = 5; 에서 x 는 어떤 값과도 항상 매칭되므로 실패할 수 없습니다.
반대로 어떤 값에 대해서는 매칭이 실패할 수 있는 패턴은 반박 가능한(refutable)
패턴입니다. 예를 들어 if let Some(x) = a_value 에서 Some(x) 는 반박 가능한
패턴입니다. a_value 가 Some 이 아니라 None 이면 이 패턴은 매칭되지 않기
때문입니다.
함수 매개변수, let 문, for 루프는 모두 반박 불가능한 패턴만 받을 수 있습니다.
값이 패턴과 맞지 않았을 때 프로그램이 할 수 있는 의미 있는 일이 없기 때문입니다.
반면 if let, while let, let...else 는 반박 가능한 패턴과 반박 불가능한
패턴 둘 다 받을 수 있습니다. 다만 컴파일러는 반박 불가능한 패턴에 대해서는 경고를
냅니다. 이런 구문은 원래 “실패할 수도 있음” 을 처리하려는 것이 목적이기 때문입니다.
조건 구문의 핵심은 성공과 실패에 따라 다르게 행동할 수 있다는 점이니까요.
일반적으로는 반박 가능성과 반박 불가능성의 차이를 너무 자주 의식할 필요는 없습니다. 하지만 오류 메시지에서 이 개념이 등장했을 때 대응할 수 있으려면, 적어도 그 뜻 정도는 알고 있어야 합니다. 그런 경우에는 코드가 의도한 동작에 따라, 패턴을 바꾸거나 패턴을 쓰는 구문 자체를 바꿔야 합니다.
이제 러스트가 반박 불가능한 패턴만 요구하는 위치에 반박 가능한 패턴을 쓰거나, 그
반대의 경우에 어떤 일이 일어나는지 예를 들어 봅시다. 목록 19-8은 let 문에
Some(x) 라는 반박 가능한 패턴을 넣은 코드입니다. 예상할 수 있듯 이 코드는
컴파일되지 않습니다.
fn main() {
let some_option_value: Option<i32> = None;
let Some(x) = some_option_value;
}
let 에 반박 가능한 패턴을 사용하려 시도하기만약 some_option_value 가 Some 이 아니라 None 이라면, Some(x) 패턴은
매칭되지 못합니다. 즉 이 패턴은 반박 가능합니다. 하지만 let 문은 반박 불가능한
패턴만 허용합니다. None 이 들어왔을 때 그 상황을 처리할 방법이 없기 때문입니다.
그래서 러스트는 “반박 불가능한 패턴이 필요한 위치에 반박 가능한 패턴을 썼다”고
컴파일 시점에 바로 알려 줍니다.
$ cargo run
Compiling patterns v0.1.0 (file:///projects/patterns)
error[E0005]: refutable pattern in local binding
--> src/main.rs:3:9
|
3 | let Some(x) = some_option_value;
| ^^^^^^^ 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
|
3 | let Some(x) = some_option_value else { todo!() };
| ++++++++++++++++
For more information about this error, try `rustc --explain E0005`.
error: could not compile `patterns` (bin "patterns") due to 1 previous error
우리는 Some(x) 패턴으로 모든 유효한 값을 포괄하지 못했기 때문에, 러스트는
정당하게 컴파일 오류를 냅니다.
반박 불가능한 패턴이 필요한 곳에 반박 가능한 패턴이 있다면, 보통 그 패턴을 사용하는
코드를 바꿔서 해결할 수 있습니다. 예를 들어 let 대신 let...else 를 사용할 수
있습니다. 그러면 패턴이 맞지 않을 때는 else 블록 안 코드가 그 값을 처리하게 됩니다.
목록 19-9가 목록 19-8의 코드를 고친 버전입니다.
fn main() {
let some_option_value: Option<i32> = None;
let Some(x) = some_option_value else {
return;
};
}
let 대신 let...else 와 반박 가능한 패턴용 블록 사용하기이제 우리는 코드에 “빠져나갈 길”을 제공한 셈입니다! 이 코드는 완전히 유효합니다.
다만 let...else 에 반박 불가능한 패턴을 주면 경고가 난다는 점은 여전히 남아 있습니다.
예를 들어 목록 19-10처럼 언제나 매칭되는 x 패턴을 넣으면 컴파일러가 경고합니다.
fn main() {
let x = 5 else {
return;
};
}
let...else 에 반박 불가능한 패턴을 사용하려 시도하기러스트는 반박 불가능한 패턴과 함께 let...else 를 쓰는 것이 의미 없다고 알려 줍니다.
$ cargo run
Compiling patterns v0.1.0 (file:///projects/patterns)
warning: irrefutable `let...else` pattern
--> src/main.rs:2:5
|
2 | let x = 5 else {
| ^^^^^^^^^
|
= note: this pattern will always match, so the `else` clause is useless
= help: consider removing the `else` clause
= note: `#[warn(irrefutable_let_patterns)]` on by default
warning: `patterns` (bin "patterns") generated 1 warning
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.39s
Running `target/debug/patterns`
이 때문에 match arm은 반박 가능한 패턴을 써야 하고, 오직 마지막 arm만
반박 불가능한 패턴으로 남은 모든 값을 받는 식으로 써야 합니다. 러스트는 arm이 하나뿐인
match 에 반박 불가능한 패턴을 쓰는 것도 허용하지만, 그런 문법은 별로 쓸모가 없고
대개는 더 단순한 let 문으로 대체할 수 있습니다.
이제 패턴을 어디에 쓰는지, 그리고 반박 가능한 패턴과 반박 불가능한 패턴이 무엇인지 알게 되었으니, 다음으로는 패턴을 만드는 데 쓸 수 있는 모든 문법을 살펴보겠습니다.