Сопоставление с образцом (Pattern Matching) — одна из самых выразительных и сильных сторон синтаксиса Rust. В отличие от банального оператора switch из других языков программирования, паттерн-матчинг в Rust умеет разрушать сложные структуры данных, проверять диапазоны, связывать переменные «на лету» и проверять динамические условия.
В прошлых статьях мы уже знакомились с базовым синтаксисом rust-enums и управляющими конструкциями ветвления rust-conditionals.
В этой статье мы погрузимся в продвинутые техники Pattern Matching: деструктуризацию глубоких структур, связывание @, охранные выражения (Match Guards) и цепочки if let.
1. Принцип полноты (Exhaustiveness) и деструктуризация
#
Ключевое правило сопоставления в Rust — полнота проверок (Exhaustiveness). Компилятор требует, чтобы ваше выражение match обрабатывало все возможные состояния входного типа.
Pattern Matching позволяет выполнять глубокую деструктуризацию — одномоментно извлекать значения из вложенных структур, кортежей и сложных вариантов enum:
2. @-связывание и Охранные выражения (Match Guards)
#
Иногда при проверке значения на вхождение в диапазон (например, 1..=10) вам нужно одновременно сохранить это значение в переменную. Для этого используется оператор @ (Binding Pattern):
num@1..=10=>println!("Число {num} в диапазоне 1..10"),
А если синтаксический шаблон требует дополнительной логической проверки, на помощь приходят Match Guards — дополнительные if-условия прямо внутри ветки match:
Изучите пошаговый пример деструктуризации и match guards в блоке ниже:
Деструктуризация, @-связывание и Match Guards
Шаг 1/2
1// Шаг 1: Глубокая деструктуризация структур и связывающие шаблоны (@)
2structAddress{ 3street: String, 4city: String, 5} 6 7structPerson{ 8name: String, 9address: Address,10}1112fnmain(){13letperson=Person{14name: String::from("Алексей"),15address: Address{16street: String::from("Ленина"),17city: String::from("Москва"),18},19};2021// 1. Извлечение полей из вложенной структуры за одно действие:
22letPerson{name,address: Address{street,city}}=person;23println!("Имя: {name}, Город: {city}, Улица: {street}");2425// 2. Использование @-связывания для сохранения значения при проверке диапазона:
26letval=15;27matchval{28num@1..=10=>println!("Число {num} в диапазоне от 1 до 10"),29num@11..=20=>println!("Число {num} в диапазоне от 11 до 20"),30_=>println!("Другое число"),31}32}33
1. Деструктуризация и @-связывание
Шаблоны позволяют разбирать вложенные структуры и перечисления за один шаг без временных переменных.
Оператор @ сохраняет привязанное значение в переменную одновременно с проверкой диапазона или шаблона.
Результат выполнения:
1enumSignal{ 2Quit, 3Move{x: i32,y: i32}, 4Write(String), 5} 6 7// Шаг 2: Охранные выражения (Match Guards) в перечислениях
8fnmain(){ 9letsignal=Signal::Move{x: 20,y: 20};1011matchsignal{12Signal::Quit=>println!("Команда на выход"),13// Match Guard с условием равенства координат:
14Signal::Move{x,y}ifx==y=>{15println!("Диагональное перемещение в точку ({x}, {y})");16}17Signal::Move{x,y}=>{18println!("Обычное смещение в точку ({x}, {y})");19}20Signal::Write(text)=>println!("Текст сигнала: {text}"),21}22}23
2. Охранные выражения (Match Guards)
Match Guard (if CONDITION) добавляет дополнительное динамическое условие к ветке match.
Компилятор учитывает порядок веток: ветку с guard следует ставить перед общей веткой.
Попробуйте выполнить пример с цепочками if let и циклом while let:
Цепочки if let и цикл while let
Шаг 1/2
1// Шаг 1: Комбинирование условий в цепочках if let (let_chains)
2fnmain(){ 3letresult: Option<Result<i32,&str>>=Some(Ok(42)); 4 5// Цепочка if let с комбинированием условий через оператор &&:
6ifletSome(res)=result&&letOk(val)=res&&val>10{ 7println!("Успешно извлечено значение больше 10: {val}"); 8} 9}10
1. Цепочки if let (let_chains)
В современных версиях Rust можно объединять несколько сопоставлений let и булевых выражений через &&.
Это избавляет от необходимости писать несколько вложенных блоков if let.
Результат выполнения:
1// Шаг 2: Использование конструкции while let для обхода итераторов
2fnmain(){ 3letnumbers=vec![10,20,30,40,50]; 4letmutiter=numbers.into_iter(); 5 6// Цикл while let извлекает элементы, пока iter.next() возвращает Some(val)
7whileletSome(val)=iter.next(){ 8println!("Обработан элемент из итератора: {val}"); 9}1011println!("Итератор завершил работу!");12}13
2. Лаконичный цикл while let
Конструкция while let выполняет цикл до тех пор, пока выражение соответствует заданному шаблону.
Идеально подходит для работы с итераторами, стек-буферами и очередями данных.