ОПРОВЕРЖИМОСТЬ: МОЖЕТ ЛИ ОБРАЗЕЦ НЕ СОВПАСТИ
Образцы бывают двух видов: опровержимые и неопровержимые. Образцы, которые
совпадают с любым возможным значением, являются неопровержимыми. Примером
служит x в выражении let x = 5;, поскольку x совпадает с чем угодно и
поэтому не может не совпасть. Образцы, которые могут не совпасть для некоторых
значений, являются опровержимыми. Вот несколько примеров:
- В выражении
if let Some(x) = a_valueобразецSome(x)является опровержимым. Если значение в переменнойa_valueравноNone, а неSome, образецSome(x)не совпадёт. - В выражении
if let &[x, ..] = a_sliceобразец&[x, ..]является опровержимым. Если значение в переменнойa_sliceимеет нулевое количество элементов, образец&[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;
}
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 мы можем
использовать if let. Тогда, если образец не совпадёт, код просто пропустит блок
в фигурных скобках, что даст ему возможность продолжить корректно. Листинг 19-9
показывает, как исправить код из листинга 19-8.
fn main() { let some_option_value: Option<i32> = None; let Some(x) = some_option_value else { return; }; }
let...else и блока с опровержимыми образцами вместо letМы дали коду выход! Этот код теперь совершенно корректен. Однако, если мы дадим
if let неопровержимый образец (образец, который всегда совпадёт), такой как x,
как показано в листинге 19-10, компилятор выдаст предупреждение.
fn main() { let x = 5 else { return; }; }
if letRust жалуется, что использование if let с неопровержимым образцом бессмысленно:
$ 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.
Теперь, когда вы знаете, где использовать образцы и в чём разница между опровержимыми и неопровержимыми образцами, давайте рассмотрим весь синтаксис, который можно использовать для создания образцов.