Усі Місця Можливого Використання Шаблонів

Шаблони з’являються в багатьох місцях в Rust, і ви ними багато користуєтеся навіть не підозрюючи про це! В цьому розділі ми розглянемо всі місця, де допускаються шаблони.

Рукави виразу match

Як обговорювалося в Розділі 6, ми використовуємо шаблони в рукавах виразів match. Формально, вирази match визначені як ключове слово match, значення яке буде зіставлятися та один або більше рукавів зіставлення, що складаються з шаблону та виразу для виконання, якщо значення зіставляється зі шаблоном рукава, як тут:

match VALUE {
    PATTERN => EXPRESSION,
    PATTERN => EXPRESSION,
    PATTERN => EXPRESSION,
}

For example, here's the match expression from Listing 6-5 that matches on an Option<i32> value in the variable x:

match x {
    None => None,
    Some(i) => Some(i + 1),
}

The patterns in this match expression are the None and Some(i) on the left of each arrow.

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

Зокрема, шаблон _ буде відповідати будь-чому, але він ніколи не зв'язується зі змінною, тому його часто використовують в останньому рукаві виразу match. Шаблон _ може бути корисним, наприклад, коли потрібно ігнорувати будь-яке не вказане значення. Ми розглянемо шаблон _ більш детально в секції "Ігнорування Значень в Шаблоні" пізніше в цьому розділі.

Умовні Вирази if let

В Розділі 6 ми обговорювали використання виразів if let в основному як рівнозначний та коротший спосіб написання match, який лише зіставляється в одному випадку. При необхідності, if let може мати відповідний else, що містить код для виконання на випадок невідповідності шаблону в if let.

В Блоці Коду 18-1 показано, що також можливо поєднувати вирази if let, else if, та else if let. Це надає нам більшу гнучкість, ніж вираз match, в якому ми можемо представити тільки одне значення для порівняння з шаблонами. Також Rust не вимагає, щоб умови в послідовності if let, else if, else if let стосувалися одна одної.

Код у Блоці Коду 18-1 визначає, яким кольором зробити ваш фон, виходячи з низки перевірок за кількома умовами. Для цього прикладу ми створили змінні з жорстко заданими значеннями, які справжня програма може отримати з вхідних даних користувача.

Файл: src/main.rs

fn main() {
    let favorite_color: Option<&str> = None;
    let is_tuesday = false;
    let age: Result<u8, _> = "34".parse();

    if let Some(color) = favorite_color {
        println!("Using your favorite color, {color}, as the background");
    } else if is_tuesday {
        println!("Tuesday is green day!");
    } else if let Ok(age) = age {
        if age > 30 {
            println!("Using purple as the background color");
        } else {
            println!("Using orange as the background color");
        }
    } else {
        println!("Using blue as the background color");
    }
}

Listing 18-1: Mixing if let, else if, else if let, and else

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

Ця умовна структура дозволяє нам підтримувати складні вимоги. З жорстко заданими значеннями як ми маємо тут, цей приклад виведе в консолі Використовую фіолетовий колір як колір фону.

Ви можете побачити, що if let також може впроваджувати затінені змінні аналогічним чином що і рукави match: рядок if let Ok(age) = age запроваджує нову затінену змінну age, яка містить значення всередині Ok. Це означає, що нам потрібно помістити умову if age > 30 в цей блок: ми не можемо об'єднати ці дві умови в if let Ok(age) = age && age > 30. Значення затіненої змінної age, яку ми хочемо порівняти з 30, не дійсне до тих пір, поки не почнеться новий діапазон з фігурної дужки.

Недоліком використання виразів if let є те, що компілятор не перевіряє вичерпність, тоді як при використанні виразів match він це робить. Якби ми пропустили останній блок else і, відповідно, пропустили обробку деяких випадків, компілятор не попередив би нас про можливу логічну помилку.

Умовні Цикли while let

Подібно до конструкції if let, умовний цикл while let дозволяє циклу while працювати допоки шаблон продовжує збігатися. У Блоці Коду наведено код циклу while let, який використовує вектор як стек і виводить в консолі значення вектора у зворотному порядку, в якому вони були додані.

fn main() {
    let mut stack = Vec::new();

    stack.push(1);
    stack.push(2);
    stack.push(3);

    while let Some(top) = stack.pop() {
        println!("{}", top);
    }
}

Listing 18-2: Using a while let loop to print values for as long as stack.pop() returns Some

Цей приклад виводить в консолі 3, 2, і потім 1. Метод pop бере останній елемент з вектора і повертає Some(значення). Якщо вектор порожній, pop поверне None. Цикл while продовжує виконання коду в своєму блоці допоки pop повертає Some. Коли pop поверне None, цикл зупиниться. Ми можемо використовувати while let для вилучення кожного елементу зі стека.

Цикли for

В циклі for, значення яке безпосередньо слідує за ключовим словом for є шаблоном. Наприклад, x в for x in y є шаблоном. Блок Коду 18-3 демонструє як використовувати шаблон в циклі for для деструктуризації або розбирання на частини кортежу, як частини циклу for.

fn main() {
    let v = vec!['a', 'b', 'c'];

    for (index, value) in v.iter().enumerate() {
        println!("{} is at index {}", value, index);
    }
}

Listing 18-3: Using a pattern in a for loop to destructure a tuple

Код в Блоці Коду 18-3 виведе в консоль наступне:

$ cargo run
   Compiling patterns v0.1.0 (file:///projects/patterns)
    Finished dev [unoptimized + debuginfo] target(s) in 0.52s
     Running `target/debug/patterns`
a is at index 0
b is at index 1
c is at index 2

Ми адаптуємо ітератор використовуючи метод enumerate таким чином, щоб він генерував значення та індекс для цього значення, поміщені в кортеж. Перше згенероване значення - кортеж (0, 'a'). При зіставленні цього значення з шаблоном (index, value), index буде 0, а value буде 'a', виводячи перший рядок виводу в консоль.

Інструкції let

До цього розділу ми явно обговорювали тільки використання шаблонів з match та if let, але насправді ми використовували шаблони і в інших місцях, в тому числі і в операторах let. Наприклад, розглянемо це просте присвоювання змінної з використанням let:

#![allow(unused)]
fn main() {
let x = 5;
}

Кожного разу, коли ви використовували інструкцію let, ви використовували шаблони, хоча, можливо, ви цього навіть не усвідомлювали! Більш формально, оператор let виглядає так:

let PATTERN = EXPRESSION;

В інструкціях типу let x = 5; з назвою змінної в слоті PATTERN назва змінної є лише особливо простою формою шаблону. Rust порівнює вираз із шаблоном і призначає будь-які знайдені імена. Таким чином, у прикладі let x = 5; x - це шаблон, який означає "прив'язати до змінної x все, що зіставляється з цим виразом." Оскільки назва x - це весь шаблон, цей шаблон фактично означає "прив'язати все до змінної x, незалежно від її значення."

To see the pattern matching aspect of let more clearly, consider Listing 18-4, which uses a pattern with let to destructure a tuple.

fn main() {
    let (x, y, z) = (1, 2, 3);
}

Listing 18-4: Using a pattern to destructure a tuple and create three variables at once

Тут ми зіставляємо кортеж з шаблоном. Rust зіставляє значення (1, 2, 3) із шаблоном (x, y, z) та бачить, що значення зіставляються, тому Rust пов'язує 1 до x, 2 до y та 3 до z. Ви можете думати про цей шаблон кортежу як про три окремих вкладених шаблонів змінних всередині.

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

fn main() {
    let (x, y) = (1, 2, 3);
}

Listing 18-5: Incorrectly constructing a pattern whose variables don’t match the number of elements in the tuple

Спроба скомпілювати цей код призведе до помилки цього типу:

$ cargo run
   Compiling patterns v0.1.0 (file:///projects/patterns)
error[E0308]: mismatched types
 --> src/main.rs:2:9
  |
2 |     let (x, y) = (1, 2, 3);
  |         ^^^^^^   --------- this expression has type `({integer}, {integer}, {integer})`
  |         |
  |         expected a tuple with 3 elements, found one with 2 elements
  |
  = note: expected tuple `({integer}, {integer}, {integer})`
             found tuple `(_, _)`

For more information about this error, try `rustc --explain E0308`.
error: could not compile `patterns` due to previous error

Щоб виправити помилку, ми можемо проігнорувати один або більше значень кортежу, використовуючи _ або .., як ви побачите в секції "Ігнорування Значень в Шаблоні" . Якщо проблема в тому, що в шаблоні занадто багато змінних, то рішення полягає в узгодженні типів шляхом видалення змінних так, щоб кількість змінних дорівнювала кількості елементів в кортежі.

Параметри Функції

Параметри функції також можуть бути шаблонами. Код у Блоці Коду 18-6, який оголошує функцію з назвою foo, яка отримує один параметр з назвою x типу i32, вже повинен виглядати знайомим.

fn foo(x: i32) {
    // code goes here
}

fn main() {}

Listing 18-6: A function signature uses patterns in the parameters

Частина x - це шаблон! Ми можемо зіставляти кортеж в аргументах функції із шаблоном, як ми зробили з let. В Блоці Коду 18-7 значення кортежу розділяються, коли ми передаємо їх до функції.

Файл: src/main.rs

fn print_coordinates(&(x, y): &(i32, i32)) {
    println!("Current location: ({}, {})", x, y);
}

fn main() {
    let point = (3, 5);
    print_coordinates(&point);
}

Listing 18-7: A function with parameters that destructure a tuple

Цей код виводить в консоль Current location: (3, 5). Значення &(3, 5) зіставляються з шаблоном &(x, y), тому x має значення 3 та yмає значення 5.

We can also use patterns in closure parameter lists in the same way as in function parameter lists, because closures are similar to functions, as discussed in Chapter 13.

Наразі ви побачили декілька способів використання шаблонів, але вони не працюють однаково у всіх місцях можливого використання. У деяких місцях шаблони мають бути неспростовні; в інших умовах вони можуть бути спростовними. Ми обговоримо ці дві концепції далі. ch18-03-pattern-syntax.html#ignoring-values-in-a-pattern