Опровержимость: может ли образец не совпасть
Образцы бывают двух видов: опровержимые и неопровержимые. Образцы, которые
совпадают с любым возможным переданным значением, называются
неопровержимыми (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;
}
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;
};
}
let...else с блоком и опровержимым образцом вместо letМы дали коду запасной выход! Этот код совершенно корректен, хотя это значит,
что использовать здесь неопровержимый образец без предупреждения нельзя. Если
дать let...else образец, который совпадает всегда, — например x, как в
листинге 19-10, — компилятор выдаст предупреждение.
fn main() {
let x = 5 else {
return;
};
}
let...elseRust жалуется, что использовать 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.
Теперь, когда вы знаете, где используются образцы и чем опровержимые отличаются от неопровержимых, разберём весь синтаксис, с помощью которого можно составлять образцы.