Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

Образцы бывают двух видов: опровержимые и неопровержимые. Образцы, которые совпадают с любым возможным переданным значением, называются неопровержимыми (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 и инструкция let...else принимают и опровержимые, и неопровержимые образцы, но на неопровержимые компилятор выдаёт предупреждение: эти конструкции по определению предназначены для обработки возможной неудачи. Весь смысл условной конструкции в том, что она ведёт себя по-разному в зависимости от успеха или неудачи.

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

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

fn main() {
    let some_option_value: Option<i32> = None;
    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.

fn main() {
    let some_option_value: Option<i32> = None;
    let Some(x) = some_option_value else {
        return;
    };
}
Листинг 19-9: let...else с блоком и опровержимым образцом вместо let

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

fn main() {
    let x = 5 else {
        return;
    };
}
Листинг 19-10: Попытка использовать неопровержимый образец с let...else

Rust жалуется, что использовать let...else с неопровержимым образцом бессмысленно: до else дело никогда не дойдёт.

$ cargo run
   Compiling patterns v0.1.0 (file:///projects/patterns)
warning: unreachable `else` clause
 --> src/main.rs:2:15
  |
2 |     let x = 5 else {
  |     --------- ^^^^
  |     |
  |     assigning to binding pattern will always succeed
  |
  = note: this pattern always matches, so the else clause is unreachable
  = 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 с одной-единственной веткой, но толку от этого мало: такой match можно заменить более простой инструкцией let.

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