Tính bác bỏ: Liệu một Mẫu Có Thể Không Khớp

Các mẫu có hai dạng: bác bỏ được (refutable) và không bác bỏ được (irrefutable). Các mẫu sẽ khớp với bất kỳ giá trị có thể nào được truyền vào được gọi là không bác bỏ được. Một ví dụ là x trong câu lệnh let x = 5; bởi vì x khớp với bất cứ thứ gì và do đó không thể thất bại khi khớp. Các mẫu có thể không khớp với một số giá trị có thể có được gọi là bác bỏ được. Một ví dụ là Some(x) trong biểu thức if let Some(x) = a_value bởi vì nếu giá trị trong biến a_valueNone thay vì Some, thì mẫu Some(x) sẽ không khớp.

Tham số hàm, câu lệnh let, và vòng lặp for chỉ có thể chấp nhận các mẫu không bác bỏ được bởi vì chương trình không thể làm bất cứ điều gì có ý nghĩa khi giá trị không khớp. Các biểu thức if letwhile let và câu lệnh let...else chấp nhận cả mẫu bác bỏ được và không bác bỏ được, nhưng trình biên dịch cảnh báo đối với các mẫu không bác bỏ được bởi vì, theo định nghĩa, chúng được dùng để xử lý khả năng thất bại: chức năng của một điều kiện nằm ở khả năng thực hiện khác nhau tùy thuộc vào thành công hay thất bại.

Nhìn chung, bạn không nên phải lo lắng về sự khác biệt giữa các mẫu bác bỏ được và không bác bỏ được; tuy nhiên, bạn cần phải làm quen với khái niệm tính bác bỏ để có thể phản ứng khi bạn thấy nó trong thông báo lỗi. Trong những trường hợp đó, bạn sẽ cần thay đổi hoặc mẫu hoặc cấu trúc mà bạn đang sử dụng với mẫu, tùy thuộc vào hành vi dự định của mã.

Hãy xem một ví dụ về điều gì xảy ra khi chúng ta cố gắng sử dụng một mẫu bác bỏ được trong khi Rust yêu cầu một mẫu không bác bỏ được và ngược lại. Listing 19-8 cho thấy một câu lệnh let, nhưng đối với mẫu, chúng ta đã chỉ định Some(x), một mẫu bác bỏ được. Như bạn có thể mong đợi, mã này sẽ không biên dịch.

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

Nếu some_option_value là một giá trị None, nó sẽ không khớp với mẫu Some(x), có nghĩa là mẫu là bác bỏ được. Tuy nhiên, câu lệnh let chỉ có thể chấp nhận một mẫu không bác bỏ được vì không có điều gì hợp lệ mà mã có thể làm với một giá trị None. Tại thời điểm biên dịch, Rust sẽ phàn nàn rằng chúng ta đã cố gắng sử dụng một mẫu bác bỏ được ở nơi yêu cầu một mẫu không bác bỏ được:

$ 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

Bởi vì chúng ta không bao quát (và không thể bao quát!) mọi giá trị hợp lệ với mẫu Some(x), Rust đúng đắn tạo ra một lỗi biên dịch.

Nếu chúng ta có một mẫu bác bỏ được ở nơi cần một mẫu không bác bỏ được, chúng ta có thể sửa nó bằng cách thay đổi mã sử dụng mẫu: thay vì sử dụng let, chúng ta có thể sử dụng if let. Sau đó nếu mẫu không khớp, mã sẽ bỏ qua phần mã trong ngoặc nhọn, cung cấp cho nó một cách để tiếp tục một cách hợp lệ. Listing 19-9 cho thấy cách sửa mã trong Listing 19-8.

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

Chúng ta đã cho mã một lối thoát! Mã này bây giờ hoàn toàn hợp lệ. Tuy nhiên, nếu chúng ta cung cấp cho if let một mẫu không bác bỏ được (một mẫu sẽ luôn khớp), chẳng hạn như x, như được hiển thị trong Listing 19-10, trình biên dịch sẽ đưa ra cảnh báo.

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

Rust phàn nàn rằng việc sử dụng if let với một mẫu không bác bỏ được là không hợp lý:

$ 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`

Vì lý do này, các nhánh của match phải sử dụng các mẫu bác bỏ được, ngoại trừ nhánh cuối cùng, nên khớp với bất kỳ giá trị còn lại nào bằng một mẫu không bác bỏ được. Rust cho phép chúng ta sử dụng một mẫu không bác bỏ được trong một match với chỉ một nhánh, nhưng cú pháp này không đặc biệt hữu ích và có thể được thay thế bằng một câu lệnh let đơn giản hơn.

Bây giờ bạn đã biết nơi sử dụng các mẫu và sự khác biệt giữa các mẫu bác bỏ được và không bác bỏ được, hãy cùng xem tất cả các cú pháp mà chúng ta có thể sử dụng để tạo mẫu.