% Шаблоны сопоставления match
Шаблоны достаточно часто используются в 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),
}
Если нас интересуют только некоторые значения, мы можем не давать имена всем составляющим:
struct Point {
x: i32,
y: i32,
}
let origin = Point { x: 0, y: 0 };
match origin {
Point { x, .. } => println!("x равен {}", x),
}
Этот код напечатает x равен 0
.
Вы можете использовать это в любом сопоставлении: не обязательно игнорировать именно первый элемент:
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>
:
# let some_value: Result<i32, &'static str> = Err("Здесь была какая-то ошибка");
match some_value {
Ok(value) => println!("получили значение: {}", value),
Err(_) => println!("произошла ошибка"),
}
В первой ветви мы привязываем значение варианта Ok
к имени value
. А в ветви
обработки варианта Err
мы используем _
, чтобы проигнорировать конкретную
ошибку, и просто печатаем общее сообщение.
_
допустим в любом шаблоне, который связывает имена. Это можно использовать,
чтобы проигнорировать части большой структуры:
fn coordinate() -> (i32, i32, i32) {
// создаём и возвращаем какой-то кортеж из трёх элементов
# (1, 2, 3)
}
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) => ...
Вот так! Существует много разных способов использования конструкции сопоставления с шаблоном, и все они могут быть смешаны и состыкованы, в зависимости от того, что вы хотите сделать:
match x {
Foo { x: Some(ref name), y: None } => ...
}
Шаблоны — это очень мощный инструмент. Используйте их.