Как работает механизм “Pattern Matching” в Rust?

Механизм сопоставления с образцом (Pattern Matching) — один из ключевых инструментов в Rust, который позволяет анализировать и разбирать значения на части, принимая разные ветви исполнения программы в зависимости от структуры данных. Он тесно связан с системой типов и концепциями enum, Option, Result, а также структур, кортежей и массивов. Этот механизм обеспечивает выразительность, безопасность и лаконичность кода.

В Rust сопоставление с образцом используется в таких конструкциях, как match, if let, while let, let, for, fn (в параметрах) и loop, позволяя "раскладывать" значения по составляющим и одновременно проверять их форму.

1. Конструкция match

match — мощный аналог switch из других языков, но с проверкой на полноту охвата вариантов (exhaustiveness). Каждая ветка сопоставляется с определённым паттерном.

Пример с enum:

enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
}
fn process(msg: Message) {
match msg {
Message::Quit => println!("Quit"),
Message::Move { x, y } => println!("Move to ({}, {})", x, y),
Message::Write(text) => println!("Text: {}", text),
}
}

Здесь каждый вариант Message сопоставляется с шаблоном. Rust требует, чтобы все возможные случаи были покрыты — либо явно, либо через _.

2. _ — подстановочный шаблон

Если вас не интересует конкретное значение или вы хотите покрыть оставшиеся случаи:

let number = 7;
match number {
1 => println!("One"),
2 | 3 => println!("Two or Three"),
_ => println!("Other"),
}

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

3. Деструктуризация структур и кортежей

Сопоставление с образцом позволяет деструктурировать значения прямо в конструкции match:

struct Point { x: i32, y: i32 }
let p = Point { x: 0, y: 7 };
match p {
Point { x: 0, y } => println!("На оси Y, y = {}", y),
Point { x, y: 0 } => println!("На оси X, x = {}", x),
Point { x, y } => println!("В точке ({}, {})", x, y),
}

Или с кортежами:

let tup = (0, 5);
match tup {
(0, y) => println!("Первый — 0, второй — {}", y),
(x, 0) => println!("Второй — 0, первый — {}", x),
_ => println!("Иное сочетание"),
}

4. Связывание и переменные в шаблонах

Внутри паттернов можно создавать переменные, которые будут захватывать значения:

let some_value = Some(10);
match some_value {
Some(x) => println!("Есть значение: {}", x),
None => println!("Пусто"),
}

5. if let и while let — упрощённые формы

if let используется, когда вас интересует только один конкретный вариант:

let color = Some("red");
if let Some(c) = color {
println!("Цвет: {}", c);
}

Аналогично while let:

let mut stack = vec!\[1, 2, 3\];
while let Some(top) = stack.pop() {
println!("Верхний элемент: {}", top);
}

6. Совмещение шаблонов (альтернатива |)

Можно указывать сразу несколько значений:

let x = 1;
match x {
1 | 2 => println!("Один или два"),
3 => println!("Три"),
_ => println!("Другое"),
}

7. Диапазоны в паттернах

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

let x = 7;
match x {
1..=5 => println!("От одного до пяти"),
6..=10 => println!("От шести до десяти"),
_ => println!("Другое"),
}

Работает с char, i32, u8 и другими типами с порядком.

8. Шаблоны с @ (связывание с проверкой)

Можно одновременно сопоставить и сохранить значение:

let msg = Message::Move { x: 5, y: 10 };
match msg {
Message::Move { x: val @ 3..=7, y } => println!("x в диапазоне: {}, y: {}", val, y),
Message::Move { x, y } => println!("x: {}, y: {}", x, y),
_ => (),
}

val @ паттерн означает: сохранить значение, если оно соответствует паттерну.

9. Игнорирование значений

Если определённое поле не важно — можно его опустить:

struct Point { x: i32, y: i32 }
let p = Point { x: 3, y: 7 };
match p {
Point { x, .. } => println!("Только x важен: {}", x),
}

Также можно использовать _:

let (x, \_) = (5, 10);

10. Сопоставление с образцом в функциях

Функции могут принимать значения, деструктурируя их сразу в параметрах:

fn print_point(Point { x, y }: Point) {
println!("Point({}, {})", x, y);
}

11. Match Guards — дополнительные условия

Можно добавлять условие после if:

let num = Some(4);
match num {
Some(x) if x < 5 => println!("Меньше 5"),
Some(x) => println!("Значение: {}", x),
None => println!("Нет значения"),
}

Это даёт гибкость, сочетая структурное сопоставление с логической проверкой.

12. Использование matches!

Хелпер-макрос для быстрой проверки:

let x = Some(2);
if matches!(x, Some(2)) {
println!("Совпадение!");
}

Полезно в выражениях и условиях.

Механизм сопоставления с образцом в Rust — это гораздо больше, чем просто проверка значений. Он даёт возможность элегантно и безопасно разбирать данные, деструктурировать структуры, проверять варианты enum, комбинировать условия, избегать вложенных if, а также поддерживать чистый и выразительный стиль программирования. Rust требует, чтобы сопоставление было полным и безопасным, что исключает ошибки, связанные с неучтёнными случаями.