Синтаксис шаблонов
В этом разделе мы рассмотрим все виды допустимого синтаксиса в шаблонах и расскажем, когда и для чего вам может понадобиться каждый из них.
Сопоставление с литералом
Как мы уже видели в главе 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 = {x:?}, y = {y}"); }
Давайте рассмотрим, что происходит, когда выполняется выражение match
. Шаблон в первой ветке не соответствует определённому значению x
, поэтому выполнение продолжается.
Шаблон во второй ветке вводит новую переменную с именем y
, которая будет соответствовать любому значению в Some
. Поскольку мы находимся в новой области видимости внутри выражения match
, это новая переменная y
, а не y
которую мы объявили в начале со значением 10. Эта новая привязка y
будет соответствовать любому значению из Some
, которое находится в x
. Следовательно, эта новая y
связывается с внутренним значением Some
из переменной x
. Этим значением является 5
, поэтому выражение для этой ветки выполняется и печатает Matched, y = 5
.
Если бы x
было значением None
вместо Some(5)
, то шаблоны в первых двух ветках не совпали бы, поэтому значение соответствовало бы подчёркиванию. Мы не ввели переменную x
в шаблоне ветки со знаком подчёркивания, поэтому x
в выражении все ещё является внешней переменной x
, которая не была затенена. В этом гипотетическом случае совпадение match
выведет Default case, x = None
.
Когда выражение match
завершается, заканчивается его область видимости как и область действия внутренней переменной y
. Последний println!
печатает at the end: x = Some(5), y = 10
.
Чтобы создать выражение match
, которое сравнивает значения внешних x
и y
, вместо введения затенённой переменной нужно использовать условие в сопоставлении образца. Мы поговорим про условие в сопоставлении шаблона позже в разделе “Дополнительные условия в сопоставлении образца”.
Группа шаблонов
В выражениях match
можно сравнивать сразу с несколькими шаблонами, используя синтаксис |
, который является оператором паттерна or. Например, в следующем примере мы сопоставляем значение x
с ветвями match, первая из которых содержит оператор or, так что если значение x
совпадёт с любым из значений в этой ветви, то будет выполнен её код:
fn main() { let x = 1; match x { 1 | 2 => println!("one or two"), 3 => println!("three"), _ => println!("anything"), } }
Будет напечатано one or two
.
Сопоставление диапазонов с помощью ..=
Синтаксис ..=
позволяет нам выполнять сравнение с диапазоном значений. В следующем коде, когда в шаблоне найдётся совпадение с любым из значений заданного диапазона, будет выполнена эта ветка:
fn main() { let x = 5; match x { 1..=5 => println!("one through five"), _ => println!("something else"), } }
Если x
равен 1, 2, 3, 4 или 5, то совпадение будет достигнуто в первой ветке. Этот синтаксис более удобен при указании нескольких значений для сравнения, чем использование оператора |
для определения этой же идеи; если бы мы решили использовать |
, нам пришлось бы написать 1 | 2 | 3 | 4 | 5
. Указание диапазона намного короче, особенно если мы хотим подобрать, скажем, любое число от 1 до 1 000!
Компилятор проверяет, что диапазон не является пустым во время компиляции, и поскольку единственными типами, для которых Rust может определить, пуст диапазон или нет, являются char
и числовые значения, диапазоны допускаются только с числовыми или char
значениями.
Вот пример использования диапазонов значений char
:
fn main() { let x = 'c'; match x { 'a'..='j' => println!("early ASCII letter"), 'k'..='z' => println!("late ASCII letter"), _ => println!("something else"), } }
Rust может сообщить, что 'c'
находится в диапазоне первого шаблона и напечатать early ASCII letter
.
Деструктуризация для получения значений
Мы также можем использовать шаблоны для деструктуризации структур, перечислений и кортежей, чтобы использовать разные части этих значений. Давайте пройдёмся по каждому варианту.
Деструктуризация структуры
В листинге 18-12 показана структура Point
с двумя полями x
и y
, которые мы можем разделить, используя шаблон с инструкцией let
.
Файл: src/main.rs
struct Point { x: i32, y: i32, } fn main() { let p = Point { x: 0, y: 7 }; let Point { x: a, y: b } = p; assert_eq!(0, a); assert_eq!(7, b); }
Этот код создаёт переменные a
и b
, которые сопоставляются значениям полей x
и y
структуры p
. Этот пример показывает, что имена переменных в шаблоне не обязательно должны совпадать с именами полей структуры. Однако обычно имена переменных сопоставляются с именами полей, чтобы было легче запомнить, какие переменные взяты из каких полей. Из-за этого, а также из-за того, что строчка let Point { x: x, y: y } = p;
содержит много дублирования, в Rust ввели специальное сокращение для шаблонов, соответствующих полям структуры: вам нужно только указать имя поля структуры, и тогда переменные, созданные из шаблона, будут иметь те же имена. Код в листинге 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); }
Этот код создаёт переменные 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})"); } } }
Первая ветвь будет соответствовать любой точке, лежащей на оси x
, если значение поля y
будет соответствовать литералу 0
. Шаблон по-прежнему создаёт переменную x
, которую мы сможем использовать в коде этой ветви.
Аналогично, вторая ветвь совпадёт с любой точкой на оси y
, в случае, если значение поля x
будет равно 0
, а для значения поля y
будет создана переменная y
. Третья ветвь не содержит никаких литералов, поэтому она соответствует любому другому Point
и создаёт переменные как для поля x
, так и для поля y
.
В этом примере значение p
совпадает по второй ветке, так как x
содержит значение 0, поэтому этот код будет печатать On the y axis at 7
.
Помните, что выражение match
перестаёт проверять следующие ветви, как только оно находит первый совпадающий шаблон, поэтому, даже если Point { x: 0, y: 0}
находится на оси x
и оси y
, этот код будет печатать только On the x axis at 0
.
Деструктуризация перечислений
Мы уже деструктурировали перечисления в книге (см., например, листинг 6-5 главы 6), но
не обсуждали явно, что шаблон для деструктуризации перечисления должен соответствовать способу объявления данных, хранящихся в перечислении. Например, в листинге 18-15 мы используем перечисление Message
из листинга 6-2 и пишем match
с шаблонами, которые будут деструктурировать каждое внутреннее значение.
Файл: src/main.rs
enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32), } fn main() { let msg = Message::ChangeColor(0, 160, 255); match msg { Message::Quit => { println!("The Quit variant has no data to destructure."); } Message::Move { x, y } => { println!("Move in the x direction {x} and in the y direction {y}"); } Message::Write(text) => { println!("Text message: {text}"); } Message::ChangeColor(r, g, b) => { println!("Change the color to red {r}, green {g}, and blue {b}") } } }
Этот код напечатает 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 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}") } _ => (), } }
Шаблон первой ветки в выражении 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
, чтобы получить ветку, обрабатывающую любые значения, которая на самом деле ничего не делает, но учитывает все оставшиеся возможные значения. Есть несколько способов игнорировать целые значения или части значений в шаблоне: используя шаблон _
(который вы видели), используя шаблон _
внутри другого шаблона, используя имя, начинающееся с подчёркивания, либо используя ..
, чтобы игнорировать оставшиеся части значения. Давайте рассмотрим, как и зачем использовать каждый из этих шаблонов.
Игнорирование всего значения с помощью шаблона _
Мы использовали подчёркивание (_
) в качестве шаблона подстановочного знака (wildcard), который будет сопоставляться с любом значением, но не будет привязываться к этому значению. Это особенно удобно в последней ветке выражения 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); }
Этот код полностью игнорирует значение 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:?}"); }
Этот код будет печатать 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}") } } }
Этот код напечатает Some numbers: 2, 8, 32
, а значения 4 и 16 будут проигнорированы.
Игнорирование неиспользуемой переменной, начинающейся с символа _
в имени
Если вы создаёте переменную, но нигде её не используете, Rust обычно выдаёт предупреждение, потому что неиспользуемая переменная может быть ошибкой. Но иногда полезно создать переменную, которую вы пока не используете, например, когда вы создаёте прототип или только начинаете проект. В этой ситуации вы можете сказать Rust не предупреждать вас о неиспользуемой переменной, начав имя переменной с подчёркивания. В листинге 18-20 мы создаём две неиспользуемые переменные, но когда мы компилируем такой код, мы должны получить предупреждение только об одной из них.
Файл: src/main.rs
fn main() { let _x = 5; let y = 10; }
Здесь мы получаем предупреждение о том, что не используем переменную y
, но мы не получаем предупреждения о неиспользовании переменной_x
.
Обратите внимание, что есть небольшая разница между использованием только _
и использованием имени, начинающегося с подчёркивания. Синтаксис _x
по-прежнему привязывает значение к переменной, тогда как _
не привязывает ничего. В листинге 18-21 представлена ошибка, показывающая, в каком случае это различие имеет значение.
fn main() {
let s = Some(String::from("Hello!"));
if let Some(_s) = s {
println!("found a string");
}
println!("{s:?}");
}
Мы получим ошибку, поскольку значение s
все равно будет перемещено в _s
, что не позволит нам больше воспользоваться s
. Однако использование подчёркивания само по себе никогда не приводит к привязке к значению. Листинг 18-22 скомпилируется без ошибок, поскольку s
не будет перемещён в _
.
fn main() { let s = Some(String::from("Hello!")); if let Some(_) = s { println!("found a string"); } println!("{s:?}"); }
Этот код работает нормально, потому что мы никогда не привязываем 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}"), } }
Мы перечисляем значение 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}"); } } }
В этом коде первое и последнее значение соответствуют first
и last
. Конструкция ..
будет соответствовать и игнорировать всё, что находится между ними.
Однако использование ..
должно быть однозначным. Если неясно, какие значения предназначены для сопоставления, а какие следует игнорировать, Rust выдаст ошибку. В листинге 18-25 показан пример неоднозначного использования ..
, поэтому он не будет компилироваться.
Файл: src/main.rs
fn main() {
let numbers = (2, 4, 8, 16, 32);
match numbers {
(.., second, ..) => {
println!("Some numbers: {second}")
},
}
}
При компиляции примера, мы получаем эту ошибку:
$ 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
, которое также должно быть выполнено, чтобы ветка была выбрана. Условия сопоставления полезны для выражения более сложных идей, чем позволяет только шаблон.
Условие может использовать переменные, созданные в шаблоне. В листинге 18-26 показан match
, в котором первая ветка имеет шаблон Some(x)
, а также имеет условие сопоставления, if x % 2 == 0
(которое будет истинным, если число чётное).
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 => (), } }
В этом примере будет напечатано The number 4 is even
. Когда num
сравнивается с шаблоном в первой ветке, он совпадает, потому что Some(4)
соответствует Some(x)
. Затем условие сопоставления проверяет, равен ли 0 остаток от деления x
на 2 и если это так, то выбирается первая ветка.
Если бы num
вместо этого было Some(5)
, условие в сопоставлении первой ветки было бы ложным, потому что остаток от 5 делённый на 2, равен 1, что не равно 0. Rust тогда перешёл бы ко второй ветке, которое совпадает, потому что вторая ветка не имеет условия сопоставления и, следовательно, соответствует любому варианту Some
.
Невозможно выразить условие if x % 2 == 0
внутри шаблона, поэтому условие в сопоставлении даёт нам возможность выразить эту логику. Недостатком этой дополнительной выразительности является то, что компилятор не пытается проверять полноту, когда задействованы выражения с условием в сопоставлении.
В листинге 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 = {x:?}, y = {y}"); }
Этот код теперь напечатает 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"), } }
Условие сопоставления гласит, что ветка совпадает, только если значение 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}"), } }
В этом примере будет напечатано Found an id in range: 5
. Указывая id_variable @
перед диапазоном 3..=7
, мы захватываем любое значение, попадающее в диапазон, одновременно проверяя, что это значение соответствует диапазону в шаблоне.
Во второй ветке, где у нас в шаблоне указан только диапазон, код этой ветки не имеет переменной, которая содержит фактическое значение поля id
. Значение поля id
могло бы быть 10, 11 или 12, но код, соответствующий этому шаблону, не знает, чему оно равно. Код шаблона не может использовать значение из поля id
, потому что мы не сохранили значение id
в переменной.
В последней ветке, где мы указали переменную без диапазона, у нас есть значение, доступное для использования в коде ветки, в переменной с именем id
. Причина в том, что мы использовали упрощённый синтаксис полей структуры. Но мы не применяли никакого сравнения со значением в поле id
в этой ветке, как мы это делали в первых двух ветках: любое значение будет соответствовать этому шаблону.
Использование @
позволяет проверять значение и сохранять его в переменной в пределах одного шаблона.
Итоги
Шаблоны Rust очень помогают различать разные виды данных. При использовании их в выражениях match
, Rust гарантирует, что ваши шаблоны охватывают все возможные значения, потому что иначе ваша программа не скомпилируется. Шаблоны в инструкциях let
и параметрах функций делают такие конструкции более полезными, позволяя разбивать элементы на более мелкие части, одновременно присваивая их значения переменным. Мы можем создавать простые или сложные шаблоны в соответствии с нашими потребностями.
Далее, в предпоследней главе книги, мы рассмотрим некоторые продвинутые аспекты различных возможностей Rust.