Синтаксис Шаблонів

У цьому розділі ми збираємо всі синтаксичні конструкції, що використовуються в шаблонах, і обговорюємо, чому і коли ви можете захотіти використовувати кожну з них.

Зіставлення з Літералами

Як ви бачили у Розділі 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);
}

Блок Коду 18-11: Вираз match з рукавом, що вводить затінену змінну y

Розглянемо, що відбувається при виконанні виразу 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);
}

Блок Коду 18-12: Деструктуризація полів структури в окремі змінні

В цьому коді створюються змінні 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);
}

Блок коду 18-13: Деструктуризація полів структури за допомогою скорочення

Цей код створить змінні 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),
    }
}

Блок Коду 18-14: Деструктуризація та зіставлення буквених значень в одному шаблоні

Перший рукав буде відповідати будь-якій точці, що лежить на осі 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 {} and in the y direction {}",
                x, y
            );
        }
        Message::Write(text) => println!("Text message: {}", text),
        Message::ChangeColor(r, g, b) => println!(
            "Change the color to red {}, green {}, and blue {}",
            r, g, b
        ),
    }
}

Блок Коду 18-15: Деструктурування варіантів енума, що містять різні види значень

Цей код виведе в консолі 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 the color to red {}, green {}, and blue {}",
            r, g, b
        ),
        Message::ChangeColor(Color::Hsv(h, s, v)) => println!(
            "Change the color to hue {}, saturation {}, and value {}",
            h, s, v
        ),
        _ => (),
    }
}

Блок Коду 18-16: Зіставлення з вкладеними енумами

Шаблон першого рукава у виразі 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);
}

Блок Коду 18-17: Використання _ в сигнатурі функції

Цей код повністю проігнорує значення 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);
}

Блок Коду 18-18: Використання підкреслення всередині шаблонів, які відповідають варіантам Some, коли нам не потрібно використовувати значення всередині Some варіанту

Цей код виведе Неможливо перезаписати існуюче користувацьке значення, а потім налаштування це 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}")
        }
    }
}

Блок Коду 18-19: Ігнорування кількох частин кортежу

Цей код виведе Деякі числа: 2, 8, 32, а значення 4 та 16 будуть проігноровані.

Ігнорування Невикористаної Змінної, Починаючи її Назву з _

Якщо ви створюєте змінну, але ніде її не використовуєте, Rust зазвичай попередить про це, оскільки невикористана змінна може бути помилкою. Однак, іноді буває корисно мати можливість створити змінну, яку ви поки що не будете використовувати, наприклад, коли ви створюєте прототип або тільки починаєте проєкт. У цій ситуації ви можете заборонити Rust попереджати вас про невикористану змінну, почавши назву змінної з символу підкреслення. У Боку Коду 18-20 ми створюємо дві невикористовувані змінні, але при компіляції цього коду ми повинні отримати попередження лише про одну з них.

Файл: src/main.rs

fn main() {
    let _x = 5;
    let y = 10;
}

Блок Коду 18-20: Початок назви змінної з символу підкреслення, щоб уникнути попередження про невикористані змінні

Тут ми отримуємо попередження про невикористання змінної y, але не отримуємо попередження про невикористання _x.

Зверніть увагу, що існує тонка різниця між використанням тільки _ і використанням імені яке починається з підкреслення. Синтаксис _x все ще прив'язує значення до змінної тоді як _ не прив'язує взагалі. Щоб показати випадок, коли ця відмінність має значення, в Блоці Коду 18-21 ми наведемо помилку.

fn main() {
    let s = Some(String::from("Hello!"));

    if let Some(_s) = s {
        println!("found a string");
    }

    println!("{:?}", s);
}

Блок Коду 18-21: Невикористана змінна, що починається з підкреслення все ще прив'язує значення, що може отримати над ним володіння

Ми отримаємо помилку, тому що значення s однаково буде переміщено в _s, що не дозволить нам використовувати s знову. Однак, використання символу підкреслення самого по собі ніколи не призведе до прив'язки до значення. Блок Коду 18-22 скомпілюється без помилок тому що s не переміщується в _.

fn main() {
    let s = Some(String::from("Hello!"));

    if let Some(_) = s {
        println!("found a string");
    }

    println!("{:?}", s);
}

Блок Коду 18-22: Використання символу підкреслення не прив'язує значення

Цей код працює, оскільки ми ніколи та ні до чого не прив'язували 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),
    }
}

Блок Коду 18-23: Ігнорування всіх полів Point крім для 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}");
        }
    }
}

Блок Коду 18-24: Порівняння тільки першого та останнього значень кортежу та ігнорування усіх інших значень

У цьому коді першому та останньому значенню відповідають first та last. .. буде зіставлятися та ігнорувати зі всім посередині.

Однак використання .. має бути однозначним. Якщо незрозуміло, які значення призначені для зіставлення, а які слід ігнорувати, Rust видасть помилку. У Блоці Коду 18-25 наведено приклад неоднозначного використання .., тому він не буде компілюватися.

Файл: src/main.rs

fn main() {
    let numbers = (2, 4, 8, 16, 32);

    match numbers {
        (.., second, ..) => {
            println!("Some numbers: {}", second)
        },
    }
}

Блок Коду 18-25: Спроба використання .. неоднозначним способом

Якщо ми скомпілюємо цей приклад, ми отримаємо цю помилку:

$ 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 => (),
    }
}

Блок Коду 18-26: Додавання запобіжника зіставлення до шаблона

Цей приклад виведе в консолі 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);
}

Блок Коду 18-27: Використання запобіжника match для перевірки рівності із зовнішньою змінною

Цей код виведе в консолі 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"),
    }
}

Блок Коду 18-28: Об'єднання декількох шаблонів за допомогою запобіжнику match

Умова 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),
    }
}

Блок коду 18-29: використання @ для зв'язування зі значенням у шаблоні і водночас перевірки

Цей приклад виведе в консолі Found an id in range: 5. Зазначивши id_variable @ перед інтервалом 3..=7 ми захоплюємо будь-яке значення, що відповідає інтервалу, перевіряючи при цьому, що значення відповідає шаблону.

У другому рукаві, де є лише інтервал, зазначений у шаблоні, код, асоційований з цим рукавом, не має змінної, яка містила б фактичне значення поля id. Значення поля id могло б бути 10, 11, або 12, але код, що йде з цим шаблоном, не знає, яким воно є. Код шаблону нездатний використати значення поля id, оскільки ми не зберегли значення id у змінній.

В останньому рукаві, де ми вказали змінну без інтервалу, ми маємо значення, доступне для використання в коді рукава, в змінній з назвою id. Це тому, що ми скористалися скороченим синтаксисом поля структури. Але ми не застосували жодної перевірки для поля id у цьому рукаві, як робили у двох перших рукавах: будь-яке значення відповідає цьому шаблону.

Використання @ дозволяє нам перевірити значення і зберегти його в змінній в одному шаблоні.

Підсумок

Шаблони в Rust дуже корисні для розрізнення між різновидами даних. При використанні у виразах match Rust гарантує, що ваші шаблони покривають усі можливі значення, бо інакше ваша програма не скомпілюється. Шаблони в інструкціях let і параметрах функцій роблять ці конструкції кориснішими, дозволяючи деструктуризацію значень на менші частини одночасно з що присвоєнням їх змінним. Ми можемо створювати прості або складні шаблони відповідно до наших потреб.

Далі, у передостанньому розділі книжки ми подивимося на деякі розширені аспекти низки функціоналів Rust.