Возможность опровержения: может ли шаблон не совпадать

Шаблоны бывают двух форм: опровержимые и неопровержимые. Шаблоны, которые будут соответствовать любому возможному переданному значению, являются неопровержимыми (irrefutable). Примером может быть x в инструкции let x = 5;, потому что x соответствует чему-либо и, следовательно, не может не совпадать. Шаблоны, которые могут не соответствовать некоторому возможному значению, являются опровержимыми (refutable). Примером может быть Some(x) в выражении if let Some(x) = a_value, потому что если значение в переменной a_value равно None, а не Some, то шаблон Some(x) не будет совпадать.

Параметры функций, инструкции let и циклы for могут принимать только неопровержимые шаблоны, поскольку программа не может сделать ничего значимого, если значения не совпадают. А выражения if let и while let принимают опровержимые и неопровержимые шаблоны, но компилятор предостерегает от неопровержимых шаблонов, поскольку по определению они предназначены для обработки возможного сбоя: функциональность условного выражения заключается в его способности выполнять разный код в зависимости от успеха или неудачи.

В общем случае, вам не нужно беспокоиться о разнице между опровержимыми (refutable) и неопровержимыми (irrefutable) шаблонами; тем не менее, вам необходимо ознакомиться с концепцией возможности опровержения, чтобы вы могли отреагировать на неё, увидев в сообщении об ошибке. В таких случаях вам потребуется изменить либо шаблон, либо конструкцию, с которой вы используете шаблон, в зависимости от предполагаемого поведения кода.

Давайте посмотрим на пример того, что происходит, когда мы пытаемся использовать опровержимый шаблон, где Rust требует неопровержимый шаблон, и наоборот. В листинге 18-8 показана инструкция let, но для образца мы указали Some(x), являющийся шаблоном, который можно опровергнуть. Как и следовало ожидать, этот код не будет компилироваться.

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

Листинг 18-8: Попытка использовать опровержимый шаблон вместе с let

Если some_option_value было бы значением None, то оно не соответствовало бы шаблону 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/ch18-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, можно использовать if let. Затем, если шаблон не совпадает, выполнение кода внутри фигурных скобок будет пропущено, что даст возможность продолжить корректное выполнение. В листинге 18-9 показано, как исправить код из листинга 18-8.

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

Листинг 18-9. Использование if let и блока с опровергнутыми шаблонами вместо let

Код исправлен! Этот код совершенно корректный, хотя это означает, что мы не можем использовать неопровержимый образец без получения ошибки. Если мы используем шаблон if let, который всегда будет совпадать, то для примера x, показанного в листинге 18-10, компилятор выдаст предупреждение.

fn main() {
    if let x = 5 {
        println!("{x}");
    };
}

Листинг 18-10. Попытка использовать неопровержимый шаблон с if let

Rust жалуется, что не имеет смысла использовать if let с неопровержимым образцом:

$ cargo run
   Compiling patterns v0.1.0 (file:///projects/patterns)
warning: irrefutable `if let` pattern
 --> src/main.rs:2:8
  |
2 |     if let x = 5 {
  |        ^^^^^^^^^
  |
  = note: this pattern will always match, so the `if let` is useless
  = help: consider replacing the `if let` with a `let`
  = 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`
5

По этой причине совпадающие ветки выражений должны использовать опровержимые шаблоны, за исключением последнего, который должен сопоставлять любые оставшиеся значения с неопровержимым шаблоном. Rust позволяет нам использовать неопровержимый шаблон в match только с одной веткой, но этот синтаксис не особенно полезен и может быть заменён более простой инструкцией let.

Теперь, когда вы знаете, где использовать шаблоны и разницу между опровержимыми и неопровержимыми шаблонами, давайте рассмотрим весь синтаксис, который мы можем использовать для создания шаблонов.