Синтаксис Шаблонів
У цьому розділі ми збираємо всі синтаксичні конструкції, що використовуються в шаблонах, і обговорюємо, чому і коли ви можете захотіти використовувати кожну з них.
Зіставлення з Літералами
Як ви бачили у Розділі 6, можна зіставляти шаблони з літералами напряму. Наведемо декілька прикладів в наступному коді:
fn main() { let x = 1; match x { 1 => println!("one"), 2 => println!("two"), 3 => println!("three"), _ => println!("anything"), } }
Цей код виведе в консолі one
, оскільки значення в x
дорівнює 1. Цей синтаксис корисний, коли ви хочете, щоб ваш код виконував дію, якщо він отримує певне значення.
Зіставлення з Іменованими Змінними
Іменовані змінні - це незаперечні шаблони, які відповідають будь-якому значенню, і ми багато разів використовували їх у книзі. Однак, існує ускладнення при використанні іменованих змінних у виразах match
. Оскільки match
починає нову область видимості, змінні, оголошені як частина шаблону всередині виразу match
, будуть затінювати змінні з тією ж назвою за межами конструкції match
, як і у випадку з усіма змінними. У Блоці Коду 18-11 оголошується змінна з назвою x
зі значенням Some(5)
та змінна y
зі значенням 10
. Потім ми створюємо вираз match
над значенням x
. Подивіться на шаблони в рукавах match і println!
наприкінці, і перед тим, як запускати цей код або читати далі, спробуйте з'ясувати, що виведе код в консолі.
Файл: src/main.rs
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 = {:?}, y = {y}", x); }
Розглянемо, що відбувається при виконанні виразу match
. Шаблон у першому рукаві порівняння не збігається із заданим значенням x
, тому код продовжується.
Шаблон у другому рукаві порівняння вводить нову змінну з назвою 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
, замість того, щоб вводити затінену змінну, нам потрібно буде використовувати умовний запобіжник. Ми поговоримо про запобіжники пізніше в розділі "Додаткові умови з запобіжниками" .
Декілька Шаблонів
У виразах match
ви можете зіставляти кілька шаблонів, використовуючи синтаксис |
, який є оператором шаблону or. Наприклад, у наступному коді ми порівнюємо значення x
з рукавами match, перше з яких має опцію or, що означає, що якщо значення 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
.
Деструктурування для Розбору Значень на Частини
Ми також використовуємо шаблони для деструктуризації структур, енумів та кортежів для використання різних частин їх значень. Розглянемо покроково кожне значення.
Деструктурування Структур
У Блоці Коду 18-12 показана структура Point
з двома полями x
і y
, яку ми можемо розбити на частини за допомогою шаблону з інструкцією let
.
Файл: src/main.rs
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 має скорочення для шаблонів, які відповідають полям struct: вам потрібно лише перерахувати назву поля struct, і змінні, створені на основі шаблону, матимуть ті ж самі назви. Блок Коду 18-13 працює так само як і Блок Коду 18-12, але змінні, що створюються в шаблоні let
, є x
і y
замість a
і b
.
Файл: src/main.rs
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
.
Ми також можемо деструктурувати за допомогою буквених значень як частини шаблону struct замість того, щоб створювати змінні для всіх полів. Це дозволяє нам перевіряти деякі з полів на наявність певних значень, створюючи змінні для деструктуризації інших полів.
У Блоці Коду 18-14 ми маємо вираз match
, який розділяє значення Point
на три випадки: точки, які лежать безпосередньо на осі x
(що вірно, коли y = 0
), на осі y
(x = 0
), або не лежать ні на одній з них.
Файл: src/main.rs
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), але ми ще окремо не обговорювали, що шаблон деструктурування енума повинен відповідати тому, як визначаються збережені в енумі дані. Як приклад, у Блоці Коду 18-15 ми використовуємо енум Message
з Блоку Коду 6-2 і пишемо match
з шаблонами, які деструктуруватимуть кожне внутрішнє значення.
Файл: src/main.rs
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 the color to red {r}, green {g}, and blue {b}",) } } }
Цей код виведе в консолі Change the color to red 0, green 160, and blue 255
. Спробуйте змінити значення msg
, щоб побачити виконання коду з інших рукавів.
Для варіантів енуму без даних, таких як Message::Quit
, ми не можемо деструктурувати значення далі. Ми тільки можемо зіставити буквальне значення Message::Quit
, і жодних змінних у цьому шаблоні немає.
Для структуро-подібних варіантів енуму, таких як Message::Move
, ми можемо використовувати шаблон схожий з тим, що ми вказували для зіставлення структур. Після назви варіанту ми ставимо фігурні дужки, а потім перелічуємо поля зі змінними, щоб розбити все на частини, які будуть використані в коді для цього рукава. Тут ми використовуємо скорочену форму, як ми це робили в Блоці Коду 18-13.
Шаблони кортежо-подібних варіантів енума, таких як Message::Write
, що містить кортеж з одним елементом, і Message::ChangeColor
, що містить кортеж з трьома елементами подібні до шаблону, який ми вказуємо для зіставлення кортежів. Кількість змінних у шаблоні повинна відповідати кількості елементів у варіанті, який ми порівнюємо.
Деструктурування Вкладених Структур та Енумів
Дотепер всі наші приклади стосувалися зіставлення структур або енумів глибиною в один рівень, але зіставлення може працювати і на вкладених елементах! Наприклад, ми можемо переробити код у Блоці Коду 18-15 для додавання підтримки RGB та HSV кольорів у повідомленні ChangeColor
, як показано у Блоці Коду 18-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
, але ми також можемо використовувати його в будь-якому шаблоні, включно з параметрами функцій, як показано в Блоці Коду 18-17.
Файл: src/main.rs
fn foo(_: i32, y: i32) { println!("This code only uses the y parameter: {}", y); } fn main() { foo(3, 4); }
Цей код повністю проігнорує значення 3
, передане першим аргументом, і виведе Даний код використовує тільки y параметр: 4
.
У більшості випадків, коли вам більше не потрібен певний параметр функції, ви змінили б підпис так, щоб він не включав невикористаний параметр. Ігнорування параметру функції може бути особливо корисним у випадках, коли, наприклад, ви реалізуєте трейт і вам потрібна сигнатура певного типу, але тіло функції у вашій реалізації не потребує жодного з параметрів. Ви тоді уникаєте попередження компілятора про невикористані параметри функції, яке було б у випадку використання назви.
Ігнорування Частин Значення з Вкладеним _
Ми також можемо використовувати _
всередині іншого шаблону, щоб ігнорувати тільки частину значення, наприклад, коли ми хочемо перевірити тільки частину значення, але не використовуємо інші частини у відповідному коді, який ми хочемо виконати. У Блоці Коду 18-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); }
Цей код виведе Неможливо перезаписати існуюче користувацьке значення
, а потім налаштування це Some(5)
. У першому рукаві match нам не потрібно зіставляти або використовувати значення всередині будь-якого з варіантів Some
, але нам потрібно перевірити випадок, коли setting_value
і new_setting_value
є варіантом Some
. У цьому випадку ми виводимо причину незмінності setting_value
, і воно не зміниться.
У всіх інших випадках (якщо або setting_value
, або new_setting_value
є None
), виражених шаблоном _
у другому плечі, ми хочемо дозволити new_setting_value
стати setting_value
.
Ми також можемо використовувати підкреслення в декількох місцях в межах одного шаблону, щоб ігнорувати певні значення. У Блоку Коду 18-19 наведено приклад ігнорування другого та четвертого значень у кортежі з п'яти елементів.
fn main() { let numbers = (2, 4, 8, 16, 32); match numbers { (first, _, third, _, fifth) => { println!("Some numbers: {first}, {third}, {fifth}") } } }
Цей код виведе Деякі числа: 2, 8, 32
, а значення 4 та 16 будуть проігноровані.
Ігнорування Невикористаної Змінної, Починаючи Її Назву з _
Якщо ви створюєте змінну, але ніде її не використовуєте, Rust зазвичай попередить про це, оскільки невикористана змінна може бути помилкою. Однак, іноді буває корисно мати можливість створити змінну, яку ви поки що не будете використовувати, наприклад, коли ви створюєте прототип або тільки починаєте проєкт. У цій ситуації ви можете заборонити Rust попереджати вас про невикористану змінну, почавши назву змінної з символу підкреслення. У Боку Коду 18-20 ми створюємо дві невикористовувані змінні, але при компіляції цього коду ми повинні отримати попередження лише про одну з них.
Файл: src/main.rs
fn main() { let _x = 5; let y = 10; }
Тут ми отримуємо попередження про невикористання змінної y
, але не отримуємо попередження про невикористання _x
.
Зверніть увагу, що існує тонка різниця між використанням тільки _
і використанням імені яке починається з підкреслення. Синтаксис _x
все ще прив'язує значення до змінної тоді як _
не прив'язує взагалі. Щоб показати випадок, коли ця відмінність має значення, в Блоці Коду 18-21 ми наведемо помилку.
fn main() {
let s = Some(String::from("Hello!"));
if let Some(_s) = s {
println!("found a string");
}
println!("{:?}", s);
}
Ми отримаємо помилку, тому що значення s
однаково буде переміщено в _s
, що не дозволить нам використовувати s
знову. Однак, використання символу підкреслення самого по собі ніколи не призведе до прив'язки до значення. Блок Коду 18-22 скомпілюється без помилок тому що s
не переміщується в _
.
fn main() { let s = Some(String::from("Hello!")); if let Some(_) = s { println!("found a string"); } println!("{:?}", s); }
Цей код працює, оскільки ми ніколи та ні до чого не прив'язували s
; воно не зміщене.
Ігнорування Решти Частин Значення з ..
Для значень, які мають багато частин, ми можемо використовувати синтаксис ..
, щоб використовувати певні частини та ігнорувати решту, уникаючи необхідності підкреслення кожного ігнорованого значення. Шаблон ..
ігнорує будь-які частини значення, які ми не зіставили явно в інших частинах шаблону. У Блоці Коду 18-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: _
, особливо коли ми працюємо зі структурами, які мають багато полів, в ситуаціях, коли тільки одне або два поля є релевантними.
Синтаксис ..
буде поширюватися на стільки значень, скільки потрібно. У Блоці Коду 18-24 показано, як використовувати ..
з кортежем.
Файл: src/main.rs
fn main() { let numbers = (2, 4, 8, 16, 32); match numbers { (first, .., last) => { println!("Some numbers: {first}, {last}"); } } }
У цьому коді першому та останньому значенню відповідають first
та last
. ..
буде зіставлятися та ігнорувати зі всім посередині.
Однак використання ..
має бути однозначним. Якщо незрозуміло, які значення призначені для зіставлення, а які слід ігнорувати, Rust видасть помилку. У Блоці Коду 18-25 наведено приклад неоднозначного використання ..
, тому він не буде компілюватися.
Файл: src/main.rs
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` due to previous error
Rust не може визначити, скільки значень у кортежі слід ігнорувати, перш ніж знайти значення з second
, а потім скільки наступних значень слід ігнорувати після цього. Цей код може означати, що ми хочемо ігнорувати 2
, пов'язати second
з 4
, а потім ігнорувати 8
, 16
та 32
; або що ми хочемо ігнорувати 2
і 4
, зв'язати second
з 8
, а потім ігнорувати 16
і 32
; тощо. Назва змінної second
нічого особливого для Rust не означає, тому ми отримуємо помилку компілятора, тому що використання ..
в двох таких місцях є неоднозначним.
Додаткові Умови з Запобіжниками Зіставлення
Запобіжник match є додатковою умовою if
, зазначеною після шаблону в рукаві match
, яка також повинна збігатися для того, щоб цей рукав було обрано. Запобіжники match корисні для вираження складніших ідей, ніж дозволяє один тільки шаблон.
В умові можуть використовуватись змінні, створені в шаблоні. У Блоці Коду 18-26 показано match
, де перший рукав має шаблон Some(x)
, а також має запобіжник match if x % 2 == 0
(що буде істинним, якщо число парне).
fn main() { let num = Some(4); match num { Some(x) if x % 2 == 0 => println!("The number {} is even", x), Some(x) => println!("The number {} is odd", x), None => (), } }
Цей приклад виведе в консолі The number 4 is even
. Коли num
порівнюється з у першому рукаві, вони збігаються, оскільки Some(4)
збігається з Some(x)
. Потім запобіжник match перевіряє, чи залишок від ділення x
на 2 дорівнює 0, і якщо це так, то вибирається перший рукав.
Якби замість num
було Some(5)
, то запобіжник match у першому рукаві був би хибним, оскільки остача від ділення 5 на 2 дорівнює 1, що не дорівнює 0. Rust потім перейде до другого рукава, який збігатиметься, тому що другий рукав не має запобіжника match і тому збігається з будь-яким варіантом Some
.
Немає можливості виразити умову if x % 2 == 0
в шаблоні, тому запобіжник дає можливість виразити цю логіку. Недоліком цієї додаткової виразності є те, що компілятор не намагатиметься перевіряти на вичерпність, коли задіяні вирази запобіжнику match.
У Блоці Коду 18-11 ми згадували, що могли б використовувати запобіжники match для вирішення нашої проблему тінізації шаблонів. Нагадаємо, що ми створили нову змінну всередині шаблону у виразі match
замість того, щоб використовувати змінну за межами match
. Ця нова змінна означала, що ми не могли перевіряти значення зовнішньої змінної. У Блоці Коду 18-27 показано, як ми можемо використовувати запобіжник match, щоб розв'язати цю проблему.
Файл: src/main.rs
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 = {:?}, y = {y}", x); }
Цей код виведе в консолі Default case, x = Some(5)
. Шаблон у другому рукаві збігу не вводить нову змінну y
, яка б затінювала зовнішню y
, що означає, що ми можемо використовувати зовнішню y
у запобіжнику match. Замість того, щоб вказати шаблон як Some(y)
, що затінило б зовнішнє y
, ми вказуємо Some(n)
. Це створює нову змінну n
, яка нічого не затінює, оскільки немає змінної n
за межами match
.
Запобіжник match if n == y
не є шаблоном і тому не вводить нових змінних. Цей y
дорівнює зовнішньому y
, а не новому затіненому y
, і ми можемо шукати значення, яке має те саме значення, що й зовнішнє y
, порівнюючи n
з y
.
Ви також можете використовувати or оператор |
в запобіжнику match для вказування декількох шаблонів; умова запобіжнику match буде застосовуватися до всіх шаблонів. У Блоці Коду 18-28 показано черговість при об'єднанні шаблону, який використовує |
з запобіжником match. Важливою частиною цього прикладу є те, що запобіжник match 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
, але запобіжник збігу if y
є хибним, тому перший рукав не обирається. Код переходить на другий рукав, який збігається, і ця програма виводить в консолі no
. Причина в тому, що умова if
застосовується до всього шаблону 4 | 5 | 6
, а не тільки до останнього значення 6
. Іншими словами, черговість запобіжнику match відносно шаблону наступна:
(4 | 5 | 6) if y => ...
замість:
4 | 5 | (6 if y) => ...
Після виконання коду, поведінка пріоритету очевидна: якби запобіжник match був застосований тільки до кінцевого значення в списку значень, заданих з допомогою оператора |
, то рукав збігся б і програма вивела б yes
.
@
Зв'язування
@
, що вимовляється "оператор at", дозволяє нам створити змінну, яка містить значення, у той час, коли ми перевіряємо значення на відповідність шаблону. У Блоці коду 18-29 ми хочемо перевірити, що поле id
у Message::Hello
є в межах 3..=7
. Ми також хочемо зв'язати значення зі змінною id_variable
, щоб ми могли використати її у коді рукава. Ми могли назвати цю змінну id
, так само як і поле, але для цього прикладу ми використаємо іншу назву.
fn main() { enum Message { Hello { id: i32 }, } let msg = Message::Hello { id: 5 }; match msg { Message::Hello { id: id_variable @ 3..=7, } => println!("Found an id in range: {}", id_variable), 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_variable @
перед інтервалом 3..=7
ми захоплюємо будь-яке значення, що відповідає інтервалу, перевіряючи при цьому, що значення відповідає шаблону.
У другому рукаві, де є лише інтервал, зазначений у шаблоні, код, асоційований з цим рукавом, не має змінної, яка містила б фактичне значення поля id
. Значення поля id
могло б бути 10, 11, або 12, але код, що йде з цим шаблоном, не знає, яким воно є. Код шаблону нездатний використати значення поля id
, оскільки ми не зберегли значення id
у змінній.
В останньому рукаві, де ми вказали змінну без інтервалу, ми маємо значення, доступне для використання в коді рукава, в змінній з назвою id
. Це тому, що ми скористалися скороченим синтаксисом поля структури. Але ми не застосували жодної перевірки для поля id
у цьому рукаві, як робили у двох перших рукавах: будь-яке значення відповідає цьому шаблону.
Використання @
дозволяє нам перевірити значення і зберегти його в змінній в одному шаблоні.
Підсумок
Шаблони в Rust дуже корисні для розрізнення між різновидами даних. При використанні у виразах match
Rust гарантує, що ваші шаблони покривають усі можливі значення, бо інакше ваша програма не скомпілюється. Шаблони в інструкціях let
і параметрах функцій роблять ці конструкції кориснішими, дозволяючи деструктуризацію значень на менші частини одночасно з що присвоєнням їх змінним. Ми можемо створювати прості або складні шаблони відповідно до наших потреб.
Далі, у передостанньому розділі книжки ми подивимося на деякі розширені аспекти низки функціоналів Rust.