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

Шаблоны бывают двух форм: опровержимые и неопровержимые. Шаблоны, которые будут соответствовать любому возможному переданному значению, являются неопровержимыми (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) образцами; тем не менее, вам необходимо ознакомиться с концепцией возможности опровержения, чтобы вы могли отреагировать на неё, увидев в сообщении об ошибке. В таких случаях вам потребуется изменить либо шаблон, либо конструкцию с которой вы используете шаблон в зависимости от предполагаемого поведения кода.

Давайте посмотрим на пример того, что происходит, когда мы пытаемся использовать опровержимый (refutable) шаблон, где 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: `None` not covered
   --> 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 `if let` to ignore the variant that isn't matched
    |
3   |     if let Some(x) = some_option_value { /* */ }
    |

error: aborting due to previous error

For more information about this error, try `rustc --explain E0005`.
error: could not compile `patterns`

To learn more, run the command again with --verbose.

Поскольку мы не покрыли (и не могли покрыть!) каждое допустимое значение с помощью образца 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:5
  |
2 | /     if let x = 5 {
3 | |         println!("{}", x);
4 | |     };
  | |_____^
  |
  = note: `#[warn(irrefutable_let_patterns)]` on by default
  = note: this pattern will always match, so the `if let` is useless
  = help: consider replacing the `if let` with a `let`

warning: 1 warning emitted

    Finished dev [unoptimized + debuginfo] target(s) in 0.39s
     Running `target/debug/patterns`
5

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

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