Паттерны представляют шаблоны, с которыми сравнивается некоторое значение. Например, возьмем конструкцию match. Ее формальное определение:
match значение{ паттерн1 => действия1, паттерн2 => действия2, паттерн3 => действия3 }
Оператор match
принимает некоторое значение и сравнивает его с набором паттернов. Если значение соответствует одному из паттернов, то
выполняются действия, предусмотренные для данного паттерна.
Возьмем простейший пример:
fn main(){ let n = 2; match n { 1 => println!("один"), 2 => println!("два"), 3 => println!("три"), _ => println!("непонятно") } }
Здесь значение переменной n
сравнивается с набором паттернов, в качестве которых выступают обычные числа. В данном случае n = 2, поэтому это
значение соответствует паттерну "2", следовательно будет выполняться выражение println!("два")
.
Однако паттерны необязательно должны представлять отдельные значения типа чисел или строк. Паттерны в Rust одновременно более гибкий и в то же время более мощный инструмент. Рассмотрим отдельные случаи применения паттернов.
В качестве паттерна может выступать набор значений. Например, с помощью оператора | мы можем указать два альтернативных выражения, которым должно соответствовать значение:
fn main(){ let n = 2; match n { 1 | 2 => println!("один или два"), 3 => println!("три"), _ => println!("непонятно") } }
Шаблон 1 | 2
указывает, что значение должно соответствовать либо числу 1, либо числу 2.
С помощью оператора последовательности ..= можно задать диапазон значений:
fn main(){ let n = 5; match n { 1..=4 => println!("от одного до четырех"), 5..=9 => println!("от пяти до девяти"), _ => println!("непонятно") } }
Шаблон 1..=4
указывает, что значение должно находиться в диапазоне от 1 до 4 включительно.
Конструкция match также позволяет сопоставить значения кортежа с некоторым набором шаблонов и выполнить соответствующие действия:
fn main() { let user = ("Bob", 37); match user{ ("Tom", 36) => println!("name: Tom, age: 36"), ("Bob", 37) => println!("name: Bob, age: 37"), _ => println!("Undefined") } }
Здесь кортеж сопоставлятся с набором шаблонов, которые представляют кортежи. Поскольку в данном случае значения кортежа user соответствует шаблону ("Bob", 37)
, то будет выполняться выражение println!("name: Bob, age: 37")
При этом при помощи шаблонов можно сравнивать только с частью значений, а также можно получать некоторые значения в переменные. Например:
fn main() { let user = ("Bob", 37); match user{ ("Tom", 36) => println!("name: Tom, age: 36"), (name, 37) => println!("name: {}", name), _ => println!("Undefined") } }
Шаблон (name, 37)
представляет шаблон, которому соответствует любой кортеж из двух элементов, где второй компонент представляет число 37.
И если кортеж соответствует этому шаблону, его первый компонент помещается в переменную name
, которую мы можем использовать в действиях,
предусмотренных для этого шаблона.
Более того мы можем целиком уйти от конкретных значений:
fn main(){ let user = ("Bob", 37); match user{ ("Tom", age) => println!("Pattern1. Name: Tom, age: {}", age), (name, 22) => println!("Pattern2. Name: {}, age: 22", name), (name, age) => println!("Pattern3. Name: {}, age: {}", name, age), } }
Два первых шаблона в конструкции match используют по одной переменной либо для первого, либо для второго компонента кортежа. Тогда как третий шаблон
использует две переменных - (name, age)
и соответствует любому кортежу, который имеет два компонента. Поскольку этот шаблон покрывает все возможные
кортежи с двумя элементами, нам не надо определять шаблон _=>
для случаев, которые не вписываются в предыдущие шаблоны.
Аналогично кортежам в конструкциях match можно использовать структуры. Например, мы можем определить для структуры шаблон с конкретными значениями:
struct Person { name: String, age: u8 } fn main(){ let bob = Person{ name: "Bob".to_string(), age: 41}; let _username = "Bob".to_string(); match bob{ Person{name: _username, age: 41} => println!("Name: Bob, age: 41"), _ => println!("Undefined person"), } }
Здесь объект структуры Person сравнивается с шаблоном Person{name: _username, age: 41}
, то есть с некоторой структурой, где поле name
равно переменной _username
, а поле age - числу 41.
Обратите внимание, что в шаблонах нельзя использовать вызовы функций, поэтому шаблон выглядит так:
Person{name: _username, age: 41}
А не так:
Person{name: "Bob".to_string(), age: 41}
Также в шаблонах для структур можно определять переменные:
struct Person { name: String, age: u8 } fn main(){ let tom = Person{ name: "Tom".to_string(), age: 36}; let _username = "Bob".to_string(); match tom{ Person{name: _username, age: 41} => println!("Name: Bob, age: 41"), Person{name, age: 36} => println!("Name: {}, age: 36", name), Person{name, age} => println!("Name: {}, age: {}", name, age) } }
Здесь первый шаблон, как и в предыдущем примере устанавливает конкретные значения.
Второй шаблон - Person{name, age: 36}
соответствует объектам структуры Person, у которых поле age равно 36. При этом значение поля name
будет помещаться в переменную name
Третий шаблон - Person{name, age}
соответствует любой структуре Person. При этом значения ее полей будут помещаться в переменные name и age.
При этом названия переменных совпадают с названиями соответствующих полей структуры. Если названия переменных не совпадают, то мы должны явным образом указать,
как они соотносятся с полями структуры:
match tom{ Person{name: _username, age: 41} => println!("Name: Bob, age: 41"), Person{name, age: 36} => println!("Name: {}, age: 36", name), Person{name: person_name, age: person_age} => println!("Name: {}, age: {}", person_name, person_age) }
Похожим образом можно использовать шаблоны для сравнения массивов:
fn main(){ let numbers = [1, 7, 3]; match numbers { [1, 2, 3] => println!("Pattern1. [1, 2, 3]"), [x, 2, 3] => println!("Pattern2. [{}, 2, 3]", x), [x, y, 3] => println!("Pattern3. [{}, {}, 3]", x, y), [x, y, z] => println!("Pattern4. [{}, {}, {}]", x, y, z), } }
Здесь конструкция match
сравнивает массив numbers с набором шаблонов, которые содержат как конкретные значения, так и переменные. В
данном случае массив numbers соответствует третьему шаблону - [x, y, 3]
. Этот шаблон соответствует любому массиву, третий элемент которого равен 3.
В случае с перечислениями шаблоны позволяют получить значения констант перечисления в переменные:
enum DayTime{ Morning(String), Evening(String) } fn main(){ let date_time = DayTime::Morning("Доброе утро".to_string()); match date_time { DayTime::Morning(message)=> println!("{}", message), DayTime::Evening(message)=> println!("{}", message) } }
Перечисление DayTime определяет две константы, которые хранят значение типа String
.
Конструкция match
предусматривает для объекта перечисления два шаблона, в каждом из которых определяется переменная message. В эту переменную
мы можем получить значение константы.
В данном случае значение переменной date_time
будет соответствовать шаблону DayTime::Morning(message)
. В итоге на консоль будет
выведено сообщение "Доброе утро".
При это константы перечисления могут представлять разные типы:
enum DayTime{ Morning(u8, u8), Evening(String) }
В данном случае константа Morning
хранит сразу два значения типа u8
(условно первый и последний утренние часы). Соответственно при определении переменной необходимо передать два значения:
let dt = DayTime::Morning(0, 12);
Тогда шаблон для этой константы может предоставить две переменные - для каждого отдельного значения константы:
enum DayTime{ Morning(u8, u8), Evening(String) } fn main(){ let morning = DayTime::Morning(0, 12); match morning { DayTime::Morning(6, end)=> println!("Утро: с 6 до {}", end), DayTime::Morning(start, end)=> println!("Утренние часы: с {} до {}", start, end), DayTime::Evening(message)=> println!("{}", message), } }
Если нам не нужны какие-то значения, вместо них также можно определить прочерк:
enum DayTime{ Morning(u8, u8), Evening(String) } fn main(){ let morning = DayTime::Morning(5, 12); match morning { DayTime::Morning(_, end)=> println!("Утренние часы заканчиваются в {} часов", end), DayTime::Evening(message)=> println!("{}", message) } }
В данном случае мы не используем первое значение константы DayTime::Morning
, однако для каждого значения константы нам надо что-то указать, например, конкретное значение или имя переменной.
А прочерк позволяет выйти из этой ситуации и вообще не использовать это значение.
То же самое можно проделать и в шаблонах для других типов. Например, для структур:
struct Person { name: String, age: u8, height: f32 } fn main(){ let bob = Person{ name: "Bob".to_string(), age: 33, height: 1.70 }; match bob { Person{name, age, height: _} => println!("name: {} age: {}", name, age) } }
В данном случае мы не собираемся использовать в конструкции match поле height, поэтому в шаблоне для этого поля указывается прочерк: Person{name, age, height: _}
.
Пример с кортежами:
fn main(){ let user = ("Tom", 36); match user { ("Bob", age) => println!("Name: Bob, age: {}", age), (name, _) => println!("Name: {}", name) } }
Если структура, кортеж, перечисление или массив содержат много значений подряд, которые нам не нужны и мы хотим их пропустить, то можно использовать оператор ... Например:
struct Person { name: String, age: u8, height: f32 } fn main(){ let bob = Person{ name: "Bob".to_string(), age: 33, height: 1.70 }; match bob { Person{name, ..} => println!("name: {}", name) } }
Шаблон Person{name, ..}
позволяет получить значение поля name, остальные поля структуры игнорируются.
Другой пример - получим только первый и последний элементы массива:
fn main(){ let numbers = [2, 3, 4, 5, 6, 7, 8]; match numbers { [first,.., last] => println!("first: {}, last: {}", first, last) } }