Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Синтаксис шаблонов

В этом разделе мы соберем весь синтаксис, допустимый в шаблонах, и обсудим, почему и когда может понадобиться использовать каждый из его вариантов.

Сопоставление литералов

Как вы видели в главе 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! в конце и попробуйте понять, что напечатает код, прежде чем запускать его или читать дальше.

Имя файла: 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 = {x:?}, y = {y}");
}
Listing 19-11: Выражение match с ветвью, которая вводит новую переменную, затеняющую существующую переменную 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. Мы поговорим об охранных условиях match позже в разделе «Добавление условий с помощью охранных условий match».

Сопоставление нескольких шаблонов

В выражениях 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.

Имя файла: 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);
}
Listing 19-12: Деструктурирование полей структуры в отдельные переменные

Этот код создает переменные a и b, которые соответствуют значениям полей x и y структуры p. Этот пример показывает, что имена переменных в шаблоне не обязаны совпадать с именами полей структуры. Однако обычно имена переменных сопоставляют с именами полей, чтобы было проще помнить, какие переменные из каких полей получены. Из-за такого распространенного использования, а также потому что запись let Point { x: x, y: y } = p; содержит много повторений, в Rust есть сокращение для шаблонов, сопоставляющих поля структуры: нужно перечислить только имена полей структуры, а переменные, созданные из шаблона, получат те же имена. Листинг 19-13 ведет себя так же, как код в листинге 19-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);
}
Listing 19-13: Деструктурирование полей структуры с помощью сокращенной записи полей

Этот код создает переменные x и y, которые соответствуют полям x и y переменной p. В результате переменные x и y содержат значения из структуры p.

Мы также можем выполнять деструктурирование с литеральными значениями как частью шаблона структуры, а не создавать переменные для всех полей. Это позволяет проверять некоторые поля на конкретные значения и одновременно создавать переменные для деструктурирования других полей.

В листинге 19-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})");
        }
    }
}
Listing 19-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), но еще не обсуждали явно, что шаблон для деструктурирования перечисления соответствует тому, как определены данные, хранящиеся внутри перечисления. В качестве примера в листинге 19-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 color to red {r}, green {g}, and blue {b}");
        }
    }
}
Listing 19-15: Деструктурирование вариантов перечисления, которые хранят разные виды значений

Этот код напечатает 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}");
        }
        _ => (),
    }
}
Listing 19-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, но его можно использовать и в любом шаблоне, включая параметры функций, как показано в листинге 19-17.

Имя файла: src/main.rs
fn foo(_: i32, y: i32) {
    println!("This code only uses the y parameter: {y}");
}

fn main() {
    foo(3, 4);
}
Listing 19-17: Использование _ в сигнатуре функции

Этот код полностью проигнорирует значение 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:?}");
}
Listing 19-18: Использование подчеркивания внутри шаблонов, сопоставляющих варианты Some, когда нам не нужно использовать значение внутри Some

Этот код напечатает 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}");
        }
    }
}
Listing 19-19: Игнорирование нескольких частей кортежа

Этот код напечатает Some numbers: 2, 8, 32, а значения 4 и 16 будут проигнорированы.

Неиспользуемая переменная, имя которой начинается с _

Если вы создаете переменную, но нигде ее не используете, Rust обычно выдает предупреждение, потому что неиспользуемая переменная может быть ошибкой. Однако иногда полезно иметь возможность создать переменную, которую вы пока не будете использовать, например при прототипировании или в самом начале проекта. В такой ситуации можно сказать Rust не предупреждать о неиспользуемой переменной, начав имя переменной с подчеркивания. В листинге 19-20 мы создаем две неиспользуемые переменные, но при компиляции этого кода должны получить предупреждение только об одной из них.

Имя файла: src/main.rs
fn main() {
    let _x = 5;
    let y = 10;
}
Listing 19-20: Начало имени переменной с подчеркивания, чтобы избежать предупреждений о неиспользуемой переменной

Здесь мы получаем предупреждение о том, что переменная y не используется, но не получаем предупреждение о неиспользовании _x.

Обратите внимание, что есть тонкое различие между использованием только _ и использованием имени, начинающегося с подчеркивания. Синтаксис _x все еще привязывает значение к переменной, тогда как _ вообще не привязывает. Чтобы показать случай, где это различие важно, листинг 19-21 даст нам ошибку.

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

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

    println!("{s:?}");
}
Listing 19-21: Неиспользуемая переменная, начинающаяся с подчеркивания, все равно привязывает значение, что может забрать владение этим значением.

Мы получим ошибку, потому что значение s все равно будет перемещено в _s, что не даст нам снова использовать s. Однако использование одного подчеркивания никогда не привязывается к значению. Листинг 19-22 скомпилируется без ошибок, потому что s не перемещается в _.

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

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

    println!("{s:?}");
}
Listing 19-22: Использование подчеркивания не привязывает значение.

Этот код работает нормально, потому что мы нигде не привязываем 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}"),
    }
}
Listing 19-23: Игнорирование всех полей Point, кроме x, с помощью ..

Мы перечисляем значение x, а затем просто включаем шаблон ... Это быстрее, чем перечислять y: _ и z: _, особенно когда мы работаем со структурами с большим числом полей в ситуациях, где важны только одно или два поля.

Синтаксис .. расширится до такого количества значений, какое потребуется. Листинг 19-24 показывает, как использовать .. с кортежем.

Имя файла: src/main.rs
fn main() {
    let numbers = (2, 4, 8, 16, 32);

    match numbers {
        (first, .., last) => {
            println!("Some numbers: {first}, {last}");
        }
    }
}
Listing 19-24: Сопоставление только первого и последнего значений в кортеже и игнорирование всех остальных значений

В этом коде первое и последнее значения сопоставляются с first и last. .. сопоставит и проигнорирует все, что находится посередине.

Однако использование .. должно быть однозначным. Если неясно, какие значения предназначены для сопоставления, а какие должны быть проигнорированы, Rust выдаст ошибку. Листинг 19-25 показывает пример неоднозначного использования .., поэтому он не скомпилируется.

Имя файла: src/main.rs
fn main() {
    let numbers = (2, 4, 8, 16, 32);

    match numbers {
        (.., second, ..) => {
            println!("Some numbers: {second}")
        },
    }
}
Listing 19-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` (bin "patterns") due to 1 previous error

Rust невозможно определить, сколько значений в кортеже нужно проигнорировать перед сопоставлением значения с second, а затем сколько дальнейших значений игнорировать после этого. Этот код мог бы означать, что мы хотим проигнорировать 2, привязать second к 4, а затем проигнорировать 8, 16 и 32; или что мы хотим проигнорировать 2 и 4, привязать second к 8, а затем проигнорировать 16 и 32; и так далее. Имя переменной second не имеет для Rust никакого специального значения, поэтому мы получаем ошибку компилятора, потому что использование .. в двух местах таким образом неоднозначно.

Добавление условий с помощью охранных условий match

Охранное условие match — это дополнительное условие if, указанное после шаблона в ветви match, которое также должно выполниться, чтобы эта ветвь была выбрана. Охранные условия match полезны для выражения более сложных идей, чем позволяет один только шаблон. Однако обратите внимание, что они доступны только в выражениях match, а не в выражениях if let или while let.

Условие может использовать переменные, созданные в шаблоне. Листинг 19-26 показывает match, где первая ветвь имеет шаблон Some(x) и также имеет охранное условие match 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 => (),
    }
}
Listing 19-26: Добавление охранного условия match к шаблону

Этот пример напечатает The number 4 is even. Когда num сравнивается с шаблоном в первой ветви, он совпадает, потому что Some(4) соответствует Some(x). Затем охранное условие match проверяет, равен ли остаток от деления x на 2 нулю, и поскольку это так, выбирается первая ветвь.

Если бы num вместо этого было Some(5), охранное условие match в первой ветви было бы false, потому что остаток от деления 5 на 2 равен 1, а это не 0. Затем Rust перешел бы ко второй ветви, которая совпала бы, потому что у второй ветви нет охранного условия match и поэтому она совпадает с любым вариантом Some.

Невозможно выразить условие if x % 2 == 0 внутри шаблона, поэтому охранное условие match дает нам возможность выразить эту логику. Недостаток этой дополнительной выразительности в том, что компилятор не пытается проверять исчерпываемость, когда задействованы выражения охранных условий match.

При обсуждении листинга 19-11 мы упоминали, что можем использовать охранные условия match, чтобы решить проблему затенения в шаблоне. Вспомните, что мы создали новую переменную внутри шаблона в выражении match вместо использования переменной вне match. Эта новая переменная означала, что мы не могли проверить значение внешней переменной. Листинг 19-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 = {x:?}, y = {y}");
}
Listing 19-27: Использование охранного условия match для проверки равенства с внешней переменной

Теперь этот код напечатает Default case, x = Some(5). Шаблон во второй ветви match не вводит новую переменную y, которая затенила бы внешнюю y, а значит, мы можем использовать внешнюю y в охранном условии match. Вместо того чтобы указать шаблон как Some(y), что затенило бы внешнюю y, мы указываем Some(n). Это создает новую переменную n, которая ничего не затеняет, потому что вне match нет переменной n.

Охранное условие match if n == y не является шаблоном и поэтому не вводит новых переменных. Эта y является внешней y, а не новой y, затеняющей ее, и мы можем искать значение, которое равно внешней y, сравнивая n с y.

Вы также можете использовать оператор или | в охранном условии match, чтобы указать несколько шаблонов; условие охранного условия match будет применяться ко всем шаблонам. Листинг 19-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"),
    }
}
Listing 19-28: Сочетание нескольких шаблонов с охранным условием match

Условие match утверждает, что ветвь совпадает только если значение x равно 4, 5 или 6 и если y равно true. Когда этот код выполняется, шаблон первой ветви совпадает, потому что x равно 4, но охранное условие match if y равно false, поэтому первая ветвь не выбирается. Код переходит ко второй ветви, которая совпадает, и эта программа печатает no. Причина в том, что условие if применяется ко всему шаблону 4 | 5 | 6, а не только к последнему значению 6. Другими словами, приоритет охранного условия match по отношению к шаблону ведет себя так:

(4 | 5 | 6) if y => ...

а не так:

4 | 5 | (6 if y) => ...

После выполнения кода поведение приоритета становится очевидным: если бы охранное условие match применялось только к последнему значению в списке значений, указанных с помощью оператора |, ветвь совпала бы, и программа напечатала бы 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}"),
    }
}
Listing 19-29: Использование @ для привязки к значению в шаблоне с одновременной его проверкой

Этот пример напечатает Found an id in range: 5. Указав id @ перед диапазоном 3..=7, мы захватываем любое значение, совпавшее с диапазоном, в переменную с именем id и одновременно проверяем, что значение совпало с шаблоном диапазона.

Во второй ветви, где в шаблоне указан только диапазон, код, связанный с ветвью, не имеет переменной, содержащей фактическое значение поля id. Значение поля id могло быть 10, 11 или 12, но код, связанный с этим шаблоном, не знает, каким именно. Код шаблона не может использовать значение из поля id, потому что мы не сохранили значение id в переменной.

В последней ветви, где мы указали переменную без диапазона, у нас есть значение, доступное для использования в коде ветви, в переменной с именем id. Причина в том, что мы использовали сокращенный синтаксис полей структуры. Но в этой ветви мы не применили к значению поля id никакой проверки, как сделали в первых двух ветвях: этому шаблону подошло бы любое значение.

Использование @ позволяет проверять значение и сохранять его в переменной в одном шаблоне.

Итоги

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

Далее, в предпоследней главе книги, мы рассмотрим некоторые расширенные аспекты разных возможностей Rust.