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

Filename: 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 guard). Мы поговорим о сторожевых условиях позже в разделе «Дополнительные условия с помощью match guards».

Несколько образцов

Вы можете сопоставлять несколько образцов с помощью синтаксиса |, который является оператором «или» для образцов. Например, в следующем коде мы сопоставляем значение 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, первая ветвь будет соответствовать. Этот синтаксис удобнее для нескольких значений сопоставления, чем использование оператора | для выражения той же идеи; если бы мы использовали |, нам пришлось бы указать 1 | 2 | 3 | 4 | 5. Указание диапазона гораздо короче, особенно если мы хотим сопоставить, скажем, любое число от 1 до 1000!

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

Filename: 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.

Filename: 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) или ни на одной из осей.

Filename: 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), но мы ещё явно не обсуждали, что образец для деструктуризации перечисления соответствует тому, как определены данные, хранящиеся внутри перечисления. В качестве примера, в листинге 19-15 мы используем перечисление Message из листинга 6-2 и пишем match с образцами, которые будут деструктурировать каждое внутреннее значение.

Filename: 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.

Filename: 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), выраженных образцом _ во второй ветви, мы хотим разрешить setting_value быть установленным в new_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 мы создаём две неиспользуемые переменные, но при компиляции этого кода мы должны получить предупреждение только об одной из них.

Filename: 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: Использование подчёркивания не привязывает значение

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

Filename: 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 показывает пример использования .. двусмысленно, поэтому он не скомпилируется.

Filename: 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 guards

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

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

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

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

Нет способа выразить условие if x % 2 == 0 внутри образца, поэтому сторожевой условие даёт нам возможность выразить эту логику. Недостаток этой дополнительной выразительности в том, что ветви со сторожевой условием не «считаются» при проверке исчерпывающего соответствия. Поэтому, даже если мы добавим Some(x) if x % 2 == 1 как дополнительную ветвь, нам всё равно понадобится незащищённая ветвь Some(x).

В листинге 19-11 мы упомянули, что можем использовать сторожевые условия, чтобы решить нашу проблему с затенением образцов. Вспомните, что мы создали новую переменную внутри образца в выражении match вместо использования переменной вне match. Эта новая переменная означала, что мы не могли проверить значение внешней переменной. Листинг 19-27 показывает, как мы можем использовать сторожевой условие, чтобы исправить эту проблему.

Filename: 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: Использование сторожевого условия для проверки равенства с внешней переменной

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

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

Вы также можете использовать оператор «или» | в сторожевом условии, чтобы указать несколько образцов; условие сторожевого условия будет применяться ко всем образцам. Листинг 19-28 показывает приоритет при объединении образца, который использует |, со сторожевой условием. Важная часть этого примера в том, что сторожевой условие 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: Объединение нескольких образцов со сторожевой условием

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

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

а не так:

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

После запуска кода поведение приоритета очевидно: если бы сторожевой условие применялось только к последнему значению в списке значений, указанных с помощью оператора |, ветвь соответствовала бы, и программа вывела бы yes.

Привязки @

Оператор at @ позволяет нам создавать переменную, которая содержит значение, одновременно с проверкой этого значения на соответствие образцу. В листинге 19-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}"),
    }
}
Listing 19-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.