Опровержимость: может ли паттерн не совпасть

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

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

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

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

#![allow(unused)]
fn main() {
// [Этот код не компилируется!]
let Some(x) = some_option_value;
}

Листинг 19-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/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. Тогда, если паттерн не совпадет, код просто пропустит код в фигурных скобках, предоставив ему возможность продолжить выполнение допустимым образом. Листинг 19-9 показывает, как исправить код из листинга 19-8.

#![allow(unused)]
fn main() {
let Some(x) = some_option_value else {
    return;
};
}

Листинг 19-9: Использование let...else и блока с опровержимыми паттернами вместо let

Мы предоставили коду запасной выход! Этот код абсолютно корректен, хотя это означает, что мы не можем использовать неопровержимый паттерн без получения предупреждения. Если мы дадим let...else паттерн, который всегда будет совпадать, такой как x, как показано в листинге 19-10, компилятор выдаст предупреждение.

#![allow(unused)]
fn main() {
let x = 5 else {
    return;
};
}

Листинг 19-10: Попытка использования неопровержимого паттерна с let...else

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.

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