Шаблоны достаточно часто используются в Rust. Мы уже использовали их в разделе
Связывание переменных, в разделе Конструкция match
, а
также в некоторых других местах. Давайте коротко пробежимся по всем
возможностям, которые можно реализовать с помощью шаблонов!
Быстро освежим в памяти: сопоставлять с шаблоном литералы можно либо напрямую,
либо с использованием символа _
, который означает любой случай:
let x = 1; match x { 1 => println!("один"), 2 => println!("два"), 3 => println!("три"), _ => println!("что угодно"), }
Этот код напечатает один
.
Вы можете сопоставлять с несколькими шаблонами, используя |
:
let x = 1; match x { 1 | 2 => println!("один или два"), 3 => println!("три"), _ => println!("что угодно"), }
Этот код напечатает один или два
.
Если вы работаете с составным типом данных, вроде struct
, вы можете
разобрать его на части («деструктурировать») внутри шаблона:
struct Point { x: i32, y: i32, } let origin = Point { x: 0, y: 0 }; match origin { Point { x, y } => println!("({},{})", x, y), }
Мы можем использовать :
, чтобы привязать значение к новому имени.
struct Point { x: i32, y: i32, } let origin = Point { x: 0, y: 0 }; match origin { Point { x: x1, y: y1 } => println!("({},{})", x1, y1), }
Если нас интересуют только некоторые значения, мы можем не давать имена всем составляющим:
fn main() { struct Point { x: i32, y: i32, } let origin = Point { x: 0, y: 0 }; match origin { Point { x, .. } => println!("x равен {}", x), } }struct Point { x: i32, y: i32, } let origin = Point { x: 0, y: 0 }; match origin { Point { x, .. } => println!("x равен {}", x), }
Этот код напечатает x равен 0
.
Вы можете использовать это в любом сопоставлении: не обязательно игнорировать именно первый элемент:
fn main() { struct Point { x: i32, y: i32, } let origin = Point { x: 0, y: 0 }; match origin { Point { y, .. } => println!("y равен {}", y), } }struct Point { x: i32, y: i32, } let origin = Point { x: 0, y: 0 }; match origin { Point { y, .. } => println!("y равен {}", y), }
Этот код напечатает y равен 0
.
Можно произвести деструктуризацию любого составного типа данных — например, кортежей и перечислений.
Вы можете использовать в шаблоне _
, чтобы проигнорировать соответствующее
значение. Например, вот сопоставление Result<T, E>
:
match some_value { Ok(value) => println!("получили значение: {}", value), Err(_) => println!("произошла ошибка"), }
В первой ветви мы привязываем значение варианта Ok
к имени value
. А в ветви
обработки варианта Err
мы используем _
, чтобы проигнорировать конкретную
ошибку, и просто печатаем общее сообщение.
_
допустим в любом шаблоне, который связывает имена. Это можно использовать,
чтобы проигнорировать части большой структуры:
fn coordinate() -> (i32, i32, i32) { // создаём и возвращаем какой-то кортеж из трёх элементов } let (x, _, z) = coordinate();
Здесь мы связываем первый и последний элемент кортежа с именами x
и z
соответственно, а второй элемент игнорируем.
Похожим образом, в шаблоне можно использовать ..
, чтобы проигнорировать
несколько значений.
enum OptionalTuple { Value(i32, i32, i32), Missing, } let x = OptionalTuple::Value(5, -2, 3); match x { OptionalTuple::Value(..) => println!("Получили кортеж!"), OptionalTuple::Missing => println!("Вот неудача."), }
Этот код печатает Получили кортеж!
.
Если вы хотите получить ссылку, то используйте ключевое слово ref
:
let x = 5; match x { ref r => println!("Получили ссылку на {}", r), }
Этот код напечатает Получили ссылку на 5
.
Здесь r
внутри match
имеет тип &i32
. Другими словами, ключевое слово ref
создает ссылку, для использования в шаблоне. Если вам нужна изменяемая ссылка,
то ref mut
будет работать аналогичным образом:
let mut x = 5; match x { ref mut mr => println!("Получили изменяемую ссылку на {}", mr), }
Вы можете сопоставлять с диапазоном значений, используя ...
:
let x = 1; match x { 1 ... 5 => println!("от одного до пяти"), _ => println!("что угодно"), }
Этот код напечатает от одного до пяти
.
Диапазоны в основном используются с числами или одиночными символами (char
).
let x = '💅'; match x { 'а' ... 'и' => println!("ранняя буква"), 'к' ... 'я' => println!("поздняя буква"), _ => println!("что-то ещё"), }
Этот код напечатает что-то ещё
.
Вы можете связать значение с именем с помощью символа @
:
let x = 1; match x { e @ 1 ... 5 => println!("получили элемент диапазона {}", e), _ => println!("что угодно"), }
Этот код напечатает получили элемент диапазона 1
. Это полезно, когда вы хотите
сделать сложное сопоставление для части структуры данных:
#[derive(Debug)] struct Person { name: Option<String>, } let name = "Steve".to_string(); let mut x: Option<Person> = Some(Person { name: Some(name) }); match x { Some(Person { name: ref a @ Some(_), .. }) => println!("{:?}", a), _ => {} }
Этот код напечатает Some("Steve")
: мы связали внутреннюю name
с a
.
Если вы используете @
совместно с |
, то вы должны убедиться, что имя
связывается в каждой из частей шаблона:
let x = 5; match x { e @ 1 ... 5 | e @ 8 ... 10 => println!("получили элемент диапазона {}", e), _ => println!("что угодно"), }
Вы можете ввести ограничители шаблонов (match guards) с помощью if
:
enum OptionalInt { Value(i32), Missing, } let x = OptionalInt::Value(5); match x { OptionalInt::Value(i) if i > 5 => println!("Получили целое больше пяти!"), OptionalInt::Value(..) => println!("Получили целое!"), OptionalInt::Missing => println!("Неудача."), }
Этот код напечатает Получили целое!
.
Если вы используете if
с несколькими шаблонами, он применяется к обоим частям:
let x = 4; let y = false; match x { 4 | 5 if y => println!("да"), _ => println!("нет"), }
Этот код печатает нет
, потому что if
применяется ко всему 4 | 5
, а не
только к 5
. Другими словами, приоритет if
выглядит так:
(4 | 5) if y => ...
а не так:
4 | (5 if y) => ...
Вот так! Существует много разных способов использования конструкции сопоставления с шаблоном, и все они могут быть смешаны и состыкованы, в зависимости от того, что вы хотите сделать:
fn main() { match x { Foo { x: Some(ref name), y: None } => ... } }match x { Foo { x: Some(ref name), y: None } => ... }
Шаблоны — это очень мощный инструмент. Используйте их.