반증 가능성: 패턴이 매칭에 실패할 수 있는가

반증 가능성: 패턴이 매칭에 실패할 수 있는가 (Refutability: Whether a Pattern Might Fail to Match)

패턴은 반증 가능한(refutable) 것과 반증 불가능한(irrefutable) 것, 두 형태가 있어요. 전달되는 어떤 값과도 일치할 패턴은 반증 불가능해요. 예를 들어 let x = 5; 문의 x는 어떤 것과도 일치하므로 매칭에 실패할 수 없죠. 어떤 가능한 값에 대해서는 매칭에 실패할 수 있는 패턴은 반증 가능해요. if let Some(x) = a_value 표현식의 Some(x)가 그 예시죠. a_value 변수의 값이 Some이 아니라 None이라면 Some(x) 패턴은 일치하지 않으니까요.

함수 매개변수, let 문, for 루프는 반증 불가능한 패턴만 받아들일 수 있어요. 값이 일치하지 않을 때 프로그램이 의미 있는 일을 할 수 없기 때문이죠. 반면 if letwhile let 표현식, let...else 문은 반증 가능한 패턴과 반증 불가능한 패턴을 모두 받아들이지만, 컴파일러는 반증 불가능한 패턴에 대해 경고해요. 정의상 그런 패턴들은 가능한 실패를 처리하도록 의도된 것이거든요. 조건부의 기능은 성공이냐 실패냐에 따라 다르게 동작하는 능력에 있으니까요.

일반적으로 반증 가능한 패턴과 반증 불가능한 패턴의 차이를 걱정할 필요는 없어요. 다만 오류 메시지에서 그걸 봤을 때 대응할 수 있도록 반증 가능성(refutability)이라는 개념은 알아둬야 해요. 그런 경우에는 코드의 의도된 동작에 따라 패턴을 바꾸거나 패턴과 함께 쓰는 구조를 바꿔야 해요.

Rust가 반증 불가능한 패턴을 요구하는 자리에 반증 가능한 패턴을 쓰려고 할 때, 그리고 그 반대일 때 어떤 일이 일어나는지 예시를 보죠. Listing 19-8은 let 문인데, 패턴으로 반증 가능한 패턴인 Some(x)를 지정했어요. 예상대로 이 코드는 컴파일되지 않아요.

fn main() {
    let some_option_value: Option<i32> = None;
    let Some(x) = some_option_value;
}

some_option_valueNone 값이라면 패턴 Some(x)와 일치하지 않을 거예요. 즉 패턴이 반증 가능하다는 뜻이죠. 하지만 let 문은 반증 불가능한 패턴만 받아들일 수 있어요. None 값으로 코드가 할 수 있는 유효한 일이 없으니까요. 컴파일 시점에 Rust는 반증 불가능한 패턴이 요구되는 곳에서 반증 가능한 패턴을 쓰려 했다고 불평할 거예요.

$ 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)로 모든 유효한 값을 다루지 못했기 때문에(그리고 다룰 수도 없었기 때문에!) Rust가 당연하게도 컴파일러 오류를 만드는 거예요.

반증 불가능한 패턴이 필요한 곳에 반증 가능한 패턴이 있다면, 패턴을 쓰는 코드를 바꿔서 고칠 수 있어요. let 대신 let...else를 쓰면 되죠. 그러면 패턴이 일치하지 않을 때 중괄호 안의 코드가 그 값을 처리할 거예요. Listing 19-9는 Listing 19-8의 코드를 고치는 방법을 보여줘요.

fn main() {
    let some_option_value: Option<i32> = None;
    let Some(x) = some_option_value else {
        return;
    };
}

코드에 탈출구를 준 거예요! 이 코드는 완벽하게 유효해요. 다만 경고 없이는 반증 불가능한 패턴을 쓸 수 없게 된다는 뜻이지만요. Listing 19-10처럼 let...else에 항상 일치할 x 같은 패턴을 주면 컴파일러가 경고할 거예요.

fn main() {
    let x = 5 else {
        return;
    };
}

Rust는 반증 불가능한 패턴과 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 갈래는 반증 가능한 패턴을 써야 하고, 마지막 갈래만 나머지 값들을 반증 불가능한 패턴으로 매칭해야 해요. Rust는 갈래가 하나뿐인 match에서 반증 불가능한 패턴을 쓰는 걸 허용하지만, 이 문법은 별로 유용하지 않고 더 단순한 let 문으로 대체될 수 있어요.

이제 패턴을 쓸 수 있는 곳과 반증 가능한 패턴·반증 불가능한 패턴의 차이를 알게 됐으니, 패턴을 만들 때 쓸 수 있는 모든 문법을 다뤄 볼게요.

출처: The Rust Book

더 알아보기 (Learn more)