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

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

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

Как мы уже видели в главе 6, можно сопоставлять с литералами напрямую. Следующий код с примерами:

fn main() {
    let x = 1;

    match x {
        1 => println!("one"),
        2 => println!("two"),
        3 => println!("three"),
        _ => println!("anything"),
    }
}

Этот код печатает one, потому что значение в x равно 1. Данный синтаксис полезен, когда вы хотите, чтобы ваш код предпринял действие, если он получает конкретное значение.

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

Именованные переменные - это неопровержимые (irrefutable) шаблоны, которые соответствуют любому значению и мы использовали их много раз в книге. Однако при использовании именованных переменных в выражениях match возникает сложность. Поскольку match начинает новую область видимости, то переменные, объявленные как часть шаблона внутри выражения match, будут затенять переменные с тем же именем вне конструкции match как и в случае со всеми переменными. В листинге 18-11 мы объявляем переменную с именем x со значением Some(5) и переменную y со значением 10. Затем мы создаём выражение match для значения x. Посмотрите на шаблоны в ветках, 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 = {:?}", x, y);
}

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

Диапазоны допускаются только с числовыми значениями или значениями типа char, поскольку компилятор проверяет, что диапазон не является пустым во время компиляции. Единственные типы, для которых Rust может определить, является ли диапазон пустым это 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; содержит много дубликатов, существует сокращение для шаблонов, которые соответствуют структурам полей. Вам нужно только перечислить имена полей структуры и переменные, созданные из шаблона, будут иметь те же имена. В листинге 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.

Вместо создания переменных для всех полей мы также можем деструктурировать с помощью литеральных значений являющихся частью структуры. Это позволяет проверить некоторые поля на определённые значения при создании переменных для деструктуризации других полей.

В листинге 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.

Деструктуризация перечислений

Ранее мы деструктурировали перечисления в книге, например, когда мы деструктурировали тип Option<i32> в листинге 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-15: Использование _ в сигнатуре функции

Этот код полностью игнорирует значение, переданное в качестве первого аргумента, 3 и выведет This code only uses the y parameter: 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

Этот код будет печатать Can't overwrite an existing customized value и затем setting is Some(5). В первой ветке нам не нужно сопоставлять или использовать значения внутри варианта 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: Игнорирование нескольких частей кортежа

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

Игнорирование неиспользуемой переменной, начинающейся с символа _ в имени

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

Файл: src/main.rs

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

Листинг 18-20: Начинаем имя переменной с подчёркивания, чтобы избежать получение предупреждения о неиспользованных переменных

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

Обратите внимание, что есть небольшая разница между использованием только _ и использованием имени, начинающегося с подчёркивания. Синтаксис _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-21: Игнорирование полей структуры 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: aborting due to previous error

error: could not compile `patterns`

To learn more, run the command again with --verbose.

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

Дополнительные условия оператора сопоставления (Match Guards)

Конструкция match guard является дополнительным условием if, указанным после шаблона в ветке match, которое также должно выполняться наряду с сопоставлением образца, чтобы ветка была выбрана. Условия сопоставления полезны для выражения более сложных идей, чем позволяет только шаблон.

Условие может использовать переменные, созданные в шаблоне. В листинге 18-26 показан match, где первая ветка имеет шаблон Some(x), а также имеет условие сопоставления if x < 5.

fn main() {
    let num = Some(4);

    match num {
        Some(x) if x < 5 => println!("less than five: {}", x),
        Some(x) => println!("{}", x),
        None => (),
    }
}

Листинг 18-26: Добавление условия сопоставления в шаблон

В этом примере будет напечатано less than five: 4. Когда num сравнивается с шаблоном в первой ветке, оно совпадает, потому что Some(4) соответствует Some(x). Условие в сопоставлении проверяет, меньше ли значение в x, чем 5 и поскольку это так, выбирается первая ветка.

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

Невозможно выразить условие if x < 5 внутри шаблона, поэтому условие в сопоставлении даёт нам возможность выразить эту логику.

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

Файл: 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 = {}", x, y);
}

Листинг 18-27. Использование условия сопоставления для проверки на равенство со значением внешней переменной

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

Условие сопоставленияif n == y не является шаблоном и следовательно, не вводит новые переменные. Переменная y является внешней y, а не новой затенённой y и мы можем искать значение, которое имеет то же значение, что и внешняя y, сравнивая значение n со значением y.

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

Листинг 18-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 (@) позволяет создать переменную, которая содержит значение, которое мы одновременно сравниваем со значением, соответствует ли оно шаблону. В листинге 18-29 показан пример, в котором мы хотим проверить, что перечисление Message::Hello со значением поля id находится в диапазоне 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.