Спростовуваність (refutability): Чи може зіставлення зі зразком не вдатися
Зразки бувають двох форм: спростовувані та неспростовувані. Зразки, які будуть
зіставлятися для будь-якого можливого переданого значення, є неспростовуваними. Прикладом був би x у
операторі let x = 5;, тому що x зіставляється з будь-чим і, отже, не може не
зіставитися. Зразки, які можуть не зіставитися для деякого можливого значення, є
спростовуваними. Прикладом був би 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;
}
Якби 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 зразок, який завжди буде зіставлятися, наприклад x, як показано в списку
19-10, компілятор видасть попередження.
fn main() {
let x = 5 else {
return;
};
}
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.
Тепер, коли ви знаєте, де використовувати зразки та в чому різниця між спростовуваними та неспростовуваними зразками, давайте розглянемо весь синтаксис, який ми можемо використовувати для створення зразків.