Синтаксис зразків
У цьому розділі ми зібраємо весь синтаксис, який є дійсним у зразках, і обговоримо чому та коли ви можете захотіти використовувати кожен із них.
Зіставлення літералів
Як ви бачили в розділі 6, ви можете зіставляти зразки з літералами безпосередньо. Наступний код наводить кілька прикладів:
fn main() {
let x = 1;
match x {
1 => println!("one"),
2 => println!("two"),
3 => println!("three"),
_ => println!("anything"),
}
}
Цей код друкує one, тому що значення в x — це 1. Цей синтаксис корисний
коли ви хочете, щоб ваш код виконав дію, якщо він отримує певне конкретне
значення.
Зіставлення іменованих змінних
Іменовані змінні — це неспростовувані зразки, які зіставляються з будь-яким значенням, і ми використовували
їх багато разів у цій книзі. Однак є ускладнення, коли ви використовуєте
іменовані змінні в виразах match, if let або while let. Оскільки кожен
із цих видів виразів починає нову область видимості, змінні, оголошені як частина
зразка всередині цих виразів, затінюватимуть ті, що мають те саме ім’я зовні
конструкцій, як і у випадку з усіма змінними. У Лістингу 19-11 ми оголошуємо
змінну на ім’я x зі значенням Some(5) і змінну y зі значенням
10. Потім ми створюємо вираз match над значенням x. Подивіться на
зразки в гілках match і println! у кінці та спробуйте з’ясувати,
що друкуватиме код, перш ніж запускати цей код або читати далі.
fn main() {
let x = Some(5);
let y = 10;
match x {
Some(50) => println!("Got 50"),
Some(y) => println!("Matched, y = {y}"),
_ => println!("Default case, x = {x:?}"),
}
println!("at the end: x = {x:?}, y = {y}");
}
Давайте розглянемо, що відбувається, коли вираз match виконується. Зразок
у першій гілці match не зіставляється з визначеним значенням x, тож код
продовжується.
Зразок у другій гілці match вводить нову змінну на ім’я y, яка
зіставлятиметься з будь-яким значенням всередині значення Some. Оскільки ми перебуваємо в новій області видимості всередині
виразу match, це нова змінна y, а не y, яку ми оголосили на
початку зі значенням 10. Це нове зв’язування y зіставлятиметься з будь-яким значенням
усередині Some, а саме це ми й маємо в x. Отже,
це нове y прив’язується до внутрішнього значення Some у x.
Це значення — 5, тож виконується вираз для цієї гілки і друкує Matched, y = 5.
Якби x був значенням None замість Some(5), зразки в перших
двох гілках не зіставилися б, тож значення зіставилося б із
підкресленням. Ми не вводили змінну x у зразку
гілки з підкресленням, тож x у виразі все ще є зовнішнім x, який не було
затіненено. У цьому гіпотетичному випадку match друкував би Default case, x = None.
Коли вираз match завершується, завершується і його область видимості, а разом із нею і область видимості
внутрішнього y. Останній println! виводить at the end: x = Some(5), y = 10.
Щоб створити вираз match, який порівнює значення зовнішніх x і
y, а не вводить нову змінну, що затінює наявну змінну y,
нам натомість потрібно було б використати умовний match guard. Про match guards ми поговоримо пізніше в розділі “Додавання умов із Match
Guards”.
Зіставлення кількох зразків
У виразах match ви можете зіставляти кілька зразків, використовуючи синтаксис |,
який є оператором або для зразків. Наприклад, у наведеному нижче коді ми
зіставляємо значення x із гілками match, перша з яких має опцію або,
що означає: якщо значення x збігається з будь-яким із значень у цій гілці,
виконається код цієї гілки:
fn main() {
let x = 1;
match x {
1 | 2 => println!("one or two"),
3 => println!("three"),
_ => println!("anything"),
}
}
Цей код друкує one or two.
Зіставлення діапазонів значень за допомогою ..=
Синтаксис ..= дозволяє нам зіставляти включний діапазон значень. У
наступному коді, коли зразок зіставляється з будь-яким із значень у заданому
діапазоні, ця гілка виконається:
fn main() {
let x = 5;
match x {
1..=5 => println!("one through five"),
_ => println!("something else"),
}
}
Якщо x дорівнює 1, 2, 3, 4 або 5, перша гілка зіставиться. Цей синтаксис
зручніший для кількох значень у match, ніж використання оператора |
для вираження тієї самої ідеї; якби ми використали |, нам довелося б вказати 1 | 2 | 3 | 4 | 5. Вказувати діапазон набагато коротше, особливо якщо ми хочемо зіставити,
скажімо, будь-яке число між 1 і 1,000!
Компілятор перевіряє, що діапазон не є порожнім, під час компіляції, і оскільки
єдиними типами, для яких Rust може визначити, чи є діапазон порожнім, є char і
числові значення, діапазони дозволені лише для числових значень або char.
Ось приклад використання діапазонів значень char:
fn main() {
let x = 'c';
match x {
'a'..='j' => println!("early ASCII letter"),
'k'..='z' => println!("late ASCII letter"),
_ => println!("something else"),
}
}
Rust може визначити, що 'c' входить до діапазону першого зразка, і друкує early ASCII letter.
Розбиття значень на частини за допомогою деструктуризації
Ми також можемо використовувати зразки для деструктуризації структур, переліків і кортежів, щоб використовувати різні частини цих значень. Давайте розглянемо кожне значення.
Структури
Лістинг 19-12 показує структуру Point із двома полями, x і y, які ми можемо
розбити на частини за допомогою зразка з оператором let.
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 0, y: 7 };
let Point { x: a, y: b } = p;
assert_eq!(0, a);
assert_eq!(7, b);
}
Цей код створює змінні a і b, які зіставляються зі значеннями полів x
і y структури p. Цей приклад показує, що імена
змінних у зразку не обов’язково мають збігатися з іменами полів структури.
Однак зазвичай змінні в зразку називають так само, як поля, щоб було
легше пам’ятати, які змінні походять із яких полів. Через це
поширене використання, а також тому, що запис let Point { x: x, y: y } = p;
містить багато повторень, Rust має скорочений запис для зразків, які зіставляються з полями структури:
вам потрібно лише перелічити назву поля структури, а змінні, створені
із зразка, матимуть ті самі імена. Лістинг 19-13 поводиться так само,
як код у Лістингу 19-12, але змінні, створені в зразку let,
— це x і y замість a і b.
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 0, y: 7 };
let Point { x, y } = p;
assert_eq!(0, x);
assert_eq!(7, y);
}
Цей код створює змінні x і y, які зіставляються з полями x і y
змінної p. Результатом є те, що змінні x і y містять
значення зі структури p.
Ми також можемо виконувати деструктуризацію з літеральними значеннями як частину зразка структури замість створення змінних для всіх полів. Це дає змогу нам перевіряти деякі поля на певні значення, створюючи змінні для деструктуризації інших полів.
У Лістингу 19-14 у нас є вираз match, який розділяє значення Point
на три випадки: точки, що лежать безпосередньо на осі x (що є правдою, коли
y = 0), на осі y (x = 0) або не на жодній осі.
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 0, y: 7 };
match p {
Point { x, y: 0 } => println!("On the x axis at {x}"),
Point { x: 0, y } => println!("On the y axis at {y}"),
Point { x, y } => {
println!("On neither axis: ({x}, {y})");
}
}
}
Перша гілка зіставиться з будь-якою точкою, що лежить на осі x, якщо вказати, що
поле y зіставляється тоді, коли його значення збігається з літералом 0. Зразок
все ще створює змінну x, яку ми можемо використовувати в коді цієї гілки.
Подібним чином, друга гілка зіставляє будь-яку точку на осі y, вказуючи, що
поле x зіставляється, якщо його значення дорівнює 0, і створює змінну y для
значення поля y. Третя гілка не вказує жодних літералів, тож вона
зіставляє будь-який інший Point і створює змінні для обох полів x і y.
У цьому прикладі значення p зіставляється з другою гілкою завдяки тому, що
x містить 0, тож цей код друкуватиме On the y axis at 7.
Пам’ятайте, що вираз match припиняє перевірку гілок, щойно знаходить
перший зразок, що зіставляється, тож навіть якщо Point { x: 0, y: 0 } лежить на осі
x і на осі y, цей код надрукував би лише On the x axis at 0.
Переліки
Ми виконували деструктуризацію переліків у цій книзі (наприклад, Лістинг 6-5 у розділі 6),
але ще не обговорювали явно, що зразок для деструктуризації переліку
відповідає способу визначення даних, які зберігаються всередині переліку. Як
приклад, у Лістингу 19-15 ми використовуємо перелік Message із Лістингу 6-2 і пишемо
match зі зразками, які деструктуризують кожне внутрішнє значення.
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn main() {
let msg = Message::ChangeColor(0, 160, 255);
match msg {
Message::Quit => {
println!("The Quit variant has no data to destructure.");
}
Message::Move { x, y } => {
println!("Move in the x direction {x} and in the y direction {y}");
}
Message::Write(text) => {
println!("Text message: {text}");
}
Message::ChangeColor(r, g, b) => {
println!("Change color to red {r}, green {g}, and blue {b}");
}
}
}
Цей код надрукує Change color to red 0, green 160, and blue 255. Спробуйте
змінити значення msg, щоб побачити, як виконуються коди з інших гілок.
Для варіантів переліку без будь-яких даних, як-от Message::Quit, ми не можемо виконати
деструктуризацію значення далі. Ми можемо лише зіставити літеральне значення Message::Quit,
і в цьому зразку немає жодних змінних.
Для подібних до структури варіантів переліку, таких як Message::Move, ми можемо використати зразок,
подібний до того, який ми вказуємо для зіставлення структур. Після назви варіанта ми
ставимо фігурні дужки, а потім перелічуємо поля зі змінними, щоб розібрати
частини для використання в коді цієї гілки. Тут ми використовуємо
скорочену форму, як і в Лістингу 19-13.
Для подібних до кортежу варіантів переліку, як-от Message::Write, що містить кортеж із
одним елементом, і Message::ChangeColor, що містить кортеж із трьома елементами,
зразок подібний до зразка, який ми вказуємо для зіставлення кортежів. Кількість
змінних у зразку має відповідати кількості елементів у варіанті, який ми
зіставляємо.
Вкладені структури й переліки
Досі всі наші приклади зіставляли структури або переліки на один рівень
глибини, але зіставлення може працювати і з вкладеними елементами! Наприклад, ми можемо
переробити код у Лістингу 19-15, щоб підтримувати кольори RGB і HSV у повідомленні
ChangeColor, як показано в Лістингу 19-16.
enum Color {
Rgb(i32, i32, i32),
Hsv(i32, i32, i32),
}
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(Color),
}
fn main() {
let msg = Message::ChangeColor(Color::Hsv(0, 160, 255));
match msg {
Message::ChangeColor(Color::Rgb(r, g, b)) => {
println!("Change color to red {r}, green {g}, and blue {b}");
}
Message::ChangeColor(Color::Hsv(h, s, v)) => {
println!("Change color to hue {h}, saturation {s}, value {v}");
}
_ => (),
}
}
Зразок першої гілки у виразі match зіставляє варіант переліку
Message::ChangeColor, який містить варіант Color::Rgb; потім
зразок прив’язується до трьох внутрішніх значень i32. Зразок другої
гілки також зіставляє варіант переліку Message::ChangeColor, але внутрішній перелік
зіставляється з Color::Hsv замість нього. Ми можемо вказати ці складні умови в одному
виразі match, навіть якщо задіяно два переліки.
Структури та кортежі
Ми можемо змішувати, поєднувати й вкладати зразки деструктуризації ще складнішими способами. Наведений нижче приклад показує складну деструктуризацію, де ми вкладаємо структури й кортежі всередину кортежу та деструктуризуємо всі примітивні значення назовні:
fn main() {
struct Point {
x: i32,
y: i32,
}
let ((feet, inches), Point { x, y }) = ((3, 10), Point { x: 3, y: -10 });
}
Цей код дає нам змогу розбивати складні типи на їхні складові частини, щоб ми могли окремо використовувати значення, які нас цікавлять.
Деструктуризація за допомогою зразків — це зручний спосіб використовувати частини значень, таких як значення з кожного поля в структурі, окремо одне від одного.
Ігнорування значень у зразку
Ви бачили, що іноді корисно ігнорувати значення в зразку, наприклад,
в останній гілці match, щоб отримати гілку для всіх інших випадків, яка фактично нічого
не робить, але охоплює всі інші можливі значення. Є кілька
способів ігнорувати цілі значення або частини значень у зразку: використовуючи
зразок _ (який ви вже бачили), використовуючи зразок _ всередині іншого зразка,
використовуючи ім’я, що починається з підкреслення, або використовуючи .., щоб ігнорувати решту
частин значення. Давайте розглянемо, як і чому використовувати кожен із цих зразків.
Усе значення з _
Ми використовували підкреслення як універсальний зразок, який зіставляється з будь-яким значенням, але не
прив’язується до значення. Це особливо корисно як остання гілка у виразі match,
але ми також можемо використовувати його в будь-якому зразку, включно з параметрами функції, як показано в Лістингу 19-17.
fn foo(_: i32, y: i32) {
println!("This code only uses the y parameter: {y}");
}
fn main() {
foo(3, 4);
}
Цей код повністю ігнорує значення 3, передане як перший аргумент,
і надрукує This code only uses the y parameter: 4.
У більшості випадків, коли вам більше не потрібен певний параметр функції, ви змінили б сигнатуру так, щоб вона не включала невикористаний параметр. Ігнорування параметра функції може бути особливо корисним у випадках, коли, наприклад, ви реалізуєте трейт, коли вам потрібен певний тип сигнатури, але тіло функції у вашій реалізації не потребує одного з параметрів. Тоді ви уникаєте попередження компілятора про невикористані параметри функції, яке ви б отримали, якби використали ім’я замість цього.
Частини значення з вкладеним _
Ми також можемо використовувати _ всередині іншого зразка, щоб ігнорувати лише частину значення,
наприклад, коли ми хочемо перевірити лише частину значення, але не маємо
використання для інших частин у відповідному коді, який хочемо запустити. Лістинг 19-18 показує код,
що відповідає за керування значенням параметра. Вимоги бізнесу такі:
користувач не повинен мати змоги перезаписати наявне налаштування
значення параметра, але може скасувати налаштування і надати йому значення, якщо воно наразі не задане.
fn main() {
let mut setting_value = Some(5);
let new_setting_value = Some(10);
match (setting_value, new_setting_value) {
(Some(_), Some(_)) => {
println!("Can't overwrite an existing customized value");
}
_ => {
setting_value = new_setting_value;
}
}
println!("setting is {setting_value:?}");
}
Цей код надрукує Can't overwrite an existing customized value, а потім
setting is Some(5). У першій гілці match нам не потрібно зіставляти або використовувати
значення всередині будь-якого варіанта Some, але нам потрібно перевірити випадок,
коли setting_value і new_setting_value є варіантом Some. У цьому
випадку ми друкуємо причину, чому не змінюємо setting_value, і він не
змінюється.
У всіх інших випадках (якщо або setting_value, або new_setting_value є None)
, виражених зразком _ у другій гілці, ми хочемо дозволити
new_setting_value стати setting_value.
Ми також можемо використовувати підкреслення в кількох місцях усередині одного зразка, щоб ігнорувати певні значення. Лістинг 19-19 показує приклад ігнорування другого і четвертого значень у кортежі з п’яти елементів.
fn main() {
let numbers = (2, 4, 8, 16, 32);
match numbers {
(first, _, third, _, fifth) => {
println!("Some numbers: {first}, {third}, {fifth}");
}
}
}
Цей код надрукує Some numbers: 2, 8, 32, а значення 4 і 16
будуть ігноровані.
Невикористана змінна, ім’я якої починається з _
Якщо ви створюєте змінну, але ніде її не використовуєте, Rust зазвичай видає попередження, тому що невикористана змінна може бути помилкою. Однак іноді корисно мати змогу створити змінну, яку ви поки що не використовуватимете, наприклад, коли ви створюєте прототип або лише починаєте проєкт. У цій ситуації ви можете сказати Rust не попереджати вас про невикористану змінну, почавши ім’я змінної з підкреслення. У Лістингу 19-20 ми створюємо дві невикористані змінні, але коли ми компілюємо цей код, маємо отримати попередження лише щодо однієї з них.
fn main() {
let _x = 5;
let y = 10;
}
Тут ми отримуємо попередження про те, що не використовуємо змінну y, але не отримуємо
попередження про те, що не використовуємо _x.
Зверніть увагу, що є тонка відмінність між використанням лише _ і використанням імені,
що починається з підкреслення. Синтаксис _x усе ще прив’язує значення до
змінної, тоді як _ взагалі не прив’язує. Щоб показати випадок, де ця
відмінність має значення, Лістинг 19-21 надасть нам помилку.
fn main() {
let s = Some(String::from("Hello!"));
if let Some(_s) = s {
println!("found a string");
}
println!("{s:?}");
}
Ми отримаємо помилку, тому що значення s усе ще буде переміщено в _s,
що заважає нам знову використати s. Однак використання самого підкреслення
ніколи не прив’язує до значення. Лістинг 19-22 скомпілюється без жодних помилок,
тому що s не переміщується в _.
fn main() {
// ANCHOR: here
let s = Some(String::from("Hello!"));
if let Some(_) = s {
println!("found a string");
}
println!("{s:?}");
// ANCHOR_END: here
}
Цей код працює цілком нормально, тому що ми ніколи ні до чого не прив’язуємо s; він не переміщується.
Решта частин значення з ..
Для значень, що мають багато частин, ми можемо використати синтаксис .., щоб використовувати певні
частини й ігнорувати решту, уникаючи потреби перелічувати підкреслення для кожного
ігнорованого значення. Зразок .. ігнорує будь-які частини значення, які ми не
зіставили явно в решті зразка. У Лістингу 19-23 у нас є
структура Point, що містить координату в тривимірному просторі. У
виразі match ми хочемо працювати лише з координатою x і ігнорувати
значення в полях y та z.
fn main() {
struct Point {
x: i32,
y: i32,
z: i32,
}
let origin = Point { x: 0, y: 0, z: 0 };
match origin {
Point { x, .. } => println!("x is {x}"),
}
}
Ми перелічуємо значення x, а потім просто додаємо зразок ... Це швидше,
ніж перелічувати y: _ і z: _, особливо коли ми працюємо зі
структурами, що мають багато полів, у ситуаціях, коли релевантними є лише одне або два поля.
Синтаксис .. розгорнеться до такої кількості значень, скільки потрібно. Лістинг 19-24
показує, як використовувати .. з кортежем.
fn main() {
let numbers = (2, 4, 8, 16, 32);
match numbers {
(first, .., last) => {
println!("Some numbers: {first}, {last}");
}
}
}
У цьому коді перше і останнє значення зіставляються з first і last.
.. зіставлятиме і ігноруватиме все посередині.
Однак використання .. має бути однозначним. Якщо незрозуміло, які значення
призначені для зіставлення, а які слід ігнорувати, Rust видасть нам помилку.
Лістинг 19-25 показує приклад неоднозначного використання .., тож він не
скомпілюється.
fn main() {
let numbers = (2, 4, 8, 16, 32);
match numbers {
(.., second, ..) => {
println!("Some numbers: {second}")
},
}
}
Коли ми компілюємо цей приклад, отримуємо таку помилку:
$ cargo run
Compiling patterns v0.1.0 (file:///projects/patterns)
error: `..` can only be used once per tuple pattern
--> src/main.rs:5:22
|
5 | (.., second, ..) => {
| -- ^^ can only be used once per tuple pattern
| |
| previously used here
error: could not compile `patterns` (bin "patterns") due to 1 previous error
Rust неможливо визначити, скільки значень у кортежі слід ігнорувати
перед зіставленням значення з second, а потім скільки наступних значень
слід ігнорувати після цього. Цей код може означати, що ми хочемо ігнорувати 2, прив’язати
second до 4, а потім ігнорувати 8, 16 і 32; або що ми хочемо ігнорувати
2 і 4, прив’язати second до 8, а потім ігнорувати 16 і 32; і так далі.
Ім’я змінної second нічого особливого для Rust не означає, тож ми отримуємо
помилку компілятора, тому що використання .. у двох місцях таке неоднозначне.
Додавання умов із Match Guards
_ Match guard_ — це додаткова умова if, зазначена після зразка в
гілці match, яка також має збігтися, щоб цю гілку було вибрано. Match guards
корисні для вираження складніших ідей, ніж дозволяє один лише зразок. Зауважте,
однак, що вони доступні лише у виразах match, а не if let або
while let.
Умова може використовувати змінні, створені в зразку. Лістинг 19-26 показує
match, де перша гілка має зразок Some(x) і також має match
guard if x % 2 == 0 (який буде true, якщо число парне).
fn main() {
let num = Some(4);
match num {
Some(x) if x % 2 == 0 => println!("The number {x} is even"),
Some(x) => println!("The number {x} is odd"),
None => (),
}
}
Цей приклад надрукує The number 4 is even. Коли num порівнюється із
зразком у першій гілці, він зіставляється, тому що Some(4) зіставляється з Some(x). Потім
match guard перевіряє, чи дорівнює остача від ділення x на 2
0, і оскільки це так, вибирається перша гілка.
Якби num був Some(5) замість цього, match guard у першій гілці
був би false, тому що остача від ділення 5 на 2 дорівнює 1, а це не
дорівнює 0. Тоді Rust перейшов би до другої гілки, яка б зіставилася, тому що
друга гілка не має match guard і тому зіставляється з будь-яким варіантом Some.
Немає способу виразити умову if x % 2 == 0 всередині зразка, тож
match guard дає нам можливість виразити цю логіку. Недолік
цієї додаткової виразності в тому, що компілятор не намагається перевіряти
повноту, коли залучені вирази match guard.
Коли ми обговорювали Лістинг 19-11, ми згадали, що могли б використати match guards, щоб
розв’язати нашу проблему затінення в зразках. Згадайте, що ми створили нову змінну
всередині зразка у виразі match замість використання змінної
зовні match. Ця нова змінна означала, що ми не могли перевірити значення
зовнішньої змінної. Лістинг 19-27 показує, як ми можемо використати match guard, щоб виправити
цю проблему.
fn main() {
let x = Some(5);
let y = 10;
match x {
Some(50) => println!("Got 50"),
Some(n) if n == y => println!("Matched, n = {n}"),
_ => println!("Default case, x = {x:?}"),
}
println!("at the end: x = {x:?}, y = {y}");
}
Тепер цей код друкуватиме Default case, x = Some(5). Зразок у другій
гілці match не вводить нову змінну y, яка б затінювала зовнішню y,
а це означає, що ми можемо використовувати зовнішню y у match guard. Замість того щоб
вказувати зразок як Some(y), що затінювало б зовнішню y, ми вказуємо
Some(n). Це створює нову змінну n, яка нічого не затінює, тому що
змінної n зовні match не існує.
Match guard if n == y не є зразком і тому не вводить нових
змінних. Це y — це саме зовнішня y, а не нова y, що її затінює, і
ми можемо шукати значення, яке має таке саме значення, як зовнішня y, порівнюючи
n з y.
Ви також можете використовувати оператор або | у match guard, щоб вказати кілька
зразків; умова match guard застосовуватиметься до всіх зразків. Лістинг
19-28 показує пріоритет при поєднанні зразка, який використовує |, із match
guard. Важлива частина цього прикладу полягає в тому, що match guard if y
застосовується до 4, 5 і 6, навіть попри те, що може здатися, ніби if y
застосовується лише до 6.
fn main() {
let x = 4;
let y = false;
match x {
4 | 5 | 6 if y => println!("yes"),
_ => println!("no"),
}
}
Умова match стверджує, що гілка зіставляється лише якщо значення x
дорівнює 4, 5 або 6 і якщо y є true. Коли цей код виконується,
зразок першої гілки зіставляється, тому що x дорівнює 4, але match guard if y
є false, тож перша гілка не вибирається. Код переходить до другої
гілки, яка справді зіставляється, і ця програма друкує no. Причина в тому, що
умова if застосовується до всього зразка 4 | 5 | 6, а не лише до останнього
значення 6. Інакше кажучи, пріоритет match guard щодо
зразка поводиться так:
(4 | 5 | 6) if y => ...
а не так:
4 | 5 | (6 if y) => ...
Після запуску коду поведінка пріоритету стає очевидною: якби match guard
застосовувався лише до фінального значення в списку значень, зазначених за допомогою
оператора |, гілка б зіставилася, і програма надрукувала б
yes.
Використання зв’язувань @
Оператор at @ дає нам змогу створити змінну, яка зберігає значення, одночасно
перевіряючи це значення на відповідність зразку. У Лістингу 19-29 ми хочемо
перевірити, що поле id Message::Hello входить до діапазону 3..=7. Ми також
хочемо прив’язати значення до змінної id, щоб ми могли використовувати його в коді,
пов’язаному з цією гілкою.
fn main() {
enum Message {
Hello { id: i32 },
}
let msg = Message::Hello { id: 5 };
match msg {
Message::Hello { id: id @ 3..=7 } => {
println!("Found an id in range: {id}")
}
Message::Hello { id: 10..=12 } => {
println!("Found an id in another range")
}
Message::Hello { id } => println!("Found some other id: {id}"),
}
}
Цей приклад надрукує Found an id in range: 5. Якщо вказати id @
перед діапазоном 3..=7, ми захоплюємо будь-яке значення, що зіставилося з діапазоном, у
змінну на ім’я id, одночасно перевіряючи, що значення зіставляється із зразком діапазону.
У другій гілці, де в зразку вказано лише діапазон, код,
пов’язаний із цією гілкою, не має змінної, що містить фактичне значення
поля id. Значення поля id могло бути 10, 11 або 12, але
код, що відповідає цьому зразку, не знає, яке саме. Код зразка
не може використовувати значення з поля id, тому що ми не зберегли
значення id у змінній.
В останній гілці, де ми вказали змінну без діапазону, у нас є
значення, доступне для використання в коді гілки у змінній на ім’я id. Причина
в тому, що ми використали скорочений синтаксис полів структури. Але ми не
застосували жодної перевірки до значення в полі id у цій гілці, як це зробили в
першій двох гілках: будь-яке значення зіставилося б із цим зразком.
Використання @ дає змогу нам перевірити значення і зберегти його в змінній в межах одного зразка.
Підсумок
Зразки Rust дуже корисні для розрізнення різних видів
даних. Коли вони використовуються у виразах match, Rust гарантує, що ваші зразки охоплюють
кожне можливе значення, інакше ваша програма не скомпілюється. Зразки в
операторах let і параметрах функцій роблять ці конструкції кориснішими, даючи змогу
деструктуризацію значень на менші частини й призначення цих частин
змінним. Ми можемо створювати прості або складні зразки відповідно до наших потреб.
Далі, у передостанньому розділі книги, ми розглянемо деякі розширені аспекти різноманітних можливостей Rust.