Как работает механизм “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 требует, чтобы сопоставление было полным и безопасным, что исключает ошибки, связанные с неучтёнными случаями.