Шаблоны сопоставления `match`

Шаблоны достаточно часто используются в Rust. Мы уже использовали их в разделе Связывание переменных, в разделе Конструкция match, а также в некоторых других местах. Давайте коротко пробежимся по всем возможностям, которые можно реализовать с помощью шаблонов!

Быстро освежим в памяти: сопоставлять с шаблоном литералы можно либо напрямую, либо с использованием символа _, который означает любой случай:

fn main() { let x = 1; match x { 1 => println!("один"), 2 => println!("два"), 3 => println!("три"), _ => println!("что угодно"), } }
let x = 1;

match x {
    1 => println!("один"),
    2 => println!("два"),
    3 => println!("три"),
    _ => println!("что угодно"),
}

Этот код напечатает один.

Сопоставление с несколькими шаблонами

Вы можете сопоставлять с несколькими шаблонами, используя |:

fn main() { let x = 1; match x { 1 | 2 => println!("один или два"), 3 => println!("три"), _ => println!("что угодно"), } }
let x = 1;

match x {
    1 | 2 => println!("один или два"),
    3 => println!("три"),
    _ => println!("что угодно"),
}

Этот код напечатает один или два.

Деструктуризация

Если вы работаете с составным типом данных, вроде struct, вы можете разобрать его на части («деструктурировать») внутри шаблона:

fn main() { 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, y } => println!("({},{})", x, y),
}

Мы можем использовать :, чтобы привязать значение к новому имени.

fn main() { 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: 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>:

fn main() { let some_value: Result<i32, &'static str> = Err("Здесь была какая-то ошибка"); match some_value { Ok(value) => println!("получили значение: {}", value), Err(_) => println!("произошла ошибка"), } }
match some_value {
    Ok(value) => println!("получили значение: {}", value),
    Err(_) => println!("произошла ошибка"),
}

В первой ветви мы привязываем значение варианта Ok к имени value. А в ветви обработки варианта Err мы используем _, чтобы проигнорировать конкретную ошибку, и просто печатаем общее сообщение.

_ допустим в любом шаблоне, который связывает имена. Это можно использовать, чтобы проигнорировать части большой структуры:

fn main() { fn coordinate() -> (i32, i32, i32) { // создаём и возвращаем какой-то кортеж из трёх элементов (1, 2, 3) } let (x, _, z) = coordinate(); }
fn coordinate() -> (i32, i32, i32) {
    // создаём и возвращаем какой-то кортеж из трёх элементов
}

let (x, _, z) = coordinate();

Здесь мы связываем первый и последний элемент кортежа с именами x и z соответственно, а второй элемент игнорируем.

Похожим образом, в шаблоне можно использовать .., чтобы проигнорировать несколько значений.

fn main() { enum OptionalTuple { Value(i32, i32, i32), Missing, } let x = OptionalTuple::Value(5, -2, 3); match x { OptionalTuple::Value(..) => println!("Получили кортеж!"), OptionalTuple::Missing => println!("Вот неудача."), } }
enum OptionalTuple {
    Value(i32, i32, i32),
    Missing,
}

let x = OptionalTuple::Value(5, -2, 3);

match x {
    OptionalTuple::Value(..) => println!("Получили кортеж!"),
    OptionalTuple::Missing => println!("Вот неудача."),
}

Этот код печатает Получили кортеж!.

ref и ref mut

Если вы хотите получить ссылку, то используйте ключевое слово ref:

fn main() { let x = 5; match x { ref r => println!("Получили ссылку на {}", r), } }
let x = 5;

match x {
    ref r => println!("Получили ссылку на {}", r),
}

Этот код напечатает Получили ссылку на 5.

Здесь r внутри match имеет тип &i32. Другими словами, ключевое слово ref создает ссылку, для использования в шаблоне. Если вам нужна изменяемая ссылка, то ref mut будет работать аналогичным образом:

fn main() { let mut x = 5; match x { ref mut mr => println!("Получили изменяемую ссылку на {}", mr), } }
let mut x = 5;

match x {
    ref mut mr => println!("Получили изменяемую ссылку на {}", mr),
}

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

Вы можете сопоставлять с диапазоном значений, используя ...:

fn main() { let x = 1; match x { 1 ... 5 => println!("от одного до пяти"), _ => println!("что угодно"), } }
let x = 1;

match x {
    1 ... 5 => println!("от одного до пяти"),
    _ => println!("что угодно"),
}

Этот код напечатает от одного до пяти.

Диапазоны в основном используются с числами или одиночными символами (char).

fn main() { let x = '💅'; match x { 'а' ... 'и' => println!("ранняя буква"), 'к' ... 'я' => println!("поздняя буква"), _ => println!("что-то ещё"), } }
let x = '💅';

match x {
    'а' ... 'и' => println!("ранняя буква"),
    'к' ... 'я' => println!("поздняя буква"),
    _ => println!("что-то ещё"),
}

Этот код напечатает что-то ещё.

Связывание

Вы можете связать значение с именем с помощью символа @:

fn main() { let x = 1; match x { e @ 1 ... 5 => println!("получили элемент диапазона {}", e), _ => println!("что угодно"), } }
let x = 1;

match x {
    e @ 1 ... 5 => println!("получили элемент диапазона {}", e),
    _ => println!("что угодно"),
}

Этот код напечатает получили элемент диапазона 1. Это полезно, когда вы хотите сделать сложное сопоставление для части структуры данных:

fn main() { #[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), _ => {} } }
#[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.

Если вы используете @ совместно с |, то вы должны убедиться, что имя связывается в каждой из частей шаблона:

fn main() { let x = 5; match x { e @ 1 ... 5 | e @ 8 ... 10 => println!("получили элемент диапазона {}", e), _ => println!("что угодно"), } }
let x = 5;

match x {
    e @ 1 ... 5 | e @ 8 ... 10 => println!("получили элемент диапазона {}", e),
    _ => println!("что угодно"),
}

Ограничители шаблонов

Вы можете ввести ограничители шаблонов (match guards) с помощью if:

fn main() { 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!("Неудача."), } }
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 с несколькими шаблонами, он применяется к обоим частям:

fn main() { let x = 4; let y = false; match x { 4 | 5 if y => println!("да"), _ => println!("нет"), } }
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 } => ...
}

Шаблоны — это очень мощный инструмент. Используйте их.