RustMiddleTechnical
Что такое pattern matching в Rust и как работает выражение match?
Pattern matching в Rust — механизм деструктуризации и ветвления, где компилятор статически проверяет полноту (exhaustiveness) покрытия всех вариантов. Выражение match сравнивает значение с паттернами последовательно и выполняет первый совпавший arm.
Базовый синтаксис match
Выражение match — это не оператор switch, а выражение, возвращающее значение. Компилятор требует покрытия всех возможных значений (exhaustive matching):
#[derive(Debug)]
enum HttpStatus {
Ok,
NotFound,
InternalError(String),
Redirect { code: u16, location: String },
}
fn describe(status: HttpStatus) -> String {
match status {
HttpStatus::Ok => String::from("200 OK"),
HttpStatus::NotFound => String::from("404 Not Found"),
HttpStatus::InternalError(msg) => format!("500: {}", msg),
HttpStatus::Redirect { code, location } => {
format!("{} -> {}", code, location)
}
}
}
Если добавить новый вариант в enum и не обновить match — ошибка компиляции. Это ключевое преимущество перед switch/case в других языках.
Паттерны: полный арсенал
let x: i32 = 7;
match x {
0 => println!("ноль"),
1..=5 => println!("от 1 до 5"), // range pattern
6 | 7 | 8 => println!("6, 7 или 8"), // or pattern
n if n > 100 => println!("большое: {}", n), // guard
_ => println!("что-то другое"), // wildcard
}
Деструктуризация структур и кортежей
struct Point { x: i32, y: i32 }
let p = Point { x: 3, y: -5 };
match p {
Point { x: 0, y } => println!("на оси Y, y={}", y),
Point { x, y: 0 } => println!("на оси X, x={}", x),
Point { x, y } if x == y => println!("на диагонали"),
Point { x, y } => println!("({}, {})", x, y),
}
// Кортежи
let pair = (true, 42);
match pair {
(true, n) => println!("true и {}", n),
(false, _) => println!("false"),
}
Binding (@) — захват с проверкой
let num = 15u32;
match num {
n @ 1..=12 => println!("месяц {}", n),
n @ 13..=19 => println!("подросток {}", n),
n => println!("другое: {}", n),
}
if let и while let — сокращённые формы
// if let: когда интересует только один вариант
let config_port: Option<u16> = Some(8080);
if let Some(port) = config_port {
println!("порт: {}", port);
}
// if let с else
if let Ok(n) = "42".parse::<u32>() {
println!("число: {}", n);
} else {
println!("не число");
}
// while let: читать из канала пока есть значения
use std::collections::VecDeque;
let mut stack: VecDeque<i32> = VecDeque::from([1, 2, 3]);
while let Some(top) = stack.pop_back() {
println!("{}", top);
}
Паттерны в let и аргументах функций
// Деструктуризация в let
let (a, b, c) = (1, 2, 3);
let Point { x, y } = Point { x: 10, y: 20 };
// Деструктуризация в аргументах
fn print_point(&(x, y): &(i32, i32)) {
println!("({}, {})", x, y);
}
// В for-цикле
let pairs = vec![(1, 'a'), (2, 'b')];
for (num, ch) in &pairs {
println!("{}: {}", num, ch);
}
matches! — макрос для булевой проверки
let status = HttpStatus::NotFound;
// Вместо: matches!(status, HttpStatus::NotFound | HttpStatus::Ok)
let is_client_or_ok = matches!(status, HttpStatus::NotFound | HttpStatus::Ok);
// Полезно в filter:
let statuses = vec![HttpStatus::Ok, HttpStatus::NotFound, HttpStatus::Ok];
let ok_count = statuses.iter().filter(|s| matches!(s, HttpStatus::Ok)).count();
Подводные камни
- Незакрытый wildcard _ скрывает ошибки: добавление
_ => unreachable!()вместо перечисления вариантов означает, что новый вариант enum не вызовет ошибку компиляции — только панику в рантайме. - Перемещение в match: match по значению перемещает его. Если нужен доступ к оригиналу после match, используйте
match &valueилиrefв паттерне. - Guard не участвует в exhaustiveness:
n if n > 0не закрывает все i32. После guard-паттерна нужен явный_ =>или паттерн без guard. - Порядок arm имеет значение: паттерны проверяются сверху вниз. Более конкретные паттерны должны идти раньше широких, иначе они никогда не сработают (компилятор предупреждает).
- Связывание в or-паттернах: в паттерне
A(x) | B(x)переменнаяxдолжна иметь одинаковый тип в обоих вариантах, иначе ошибка компиляции. - Деструктуризация с ..:
Point { x, .. }игнорирует остальные поля. Удобно, но при добавлении поля в структуру компилятор не напомнит обновить match. - match на строках: нельзя использовать String в паттернах напрямую — только &str.
match s.as_str() { "foo" => ... }. - Вложенный match vs комбинаторы: глубокая вложенность match снижает читаемость. Для Option/Result предпочтительны
map,and_then,?.
Common mistakes
- Отвечать определением без production-сценария.
- Не называть runtime boundary, security boundary или failure mode.
- Игнорировать версию API, observability и тестовую проверку.
What the interviewer is testing
- Объясняет механизм своими словами и без выдуманных API.
- Называет реальные риски, диагностику и критерий корректности.
- Связывает ответ с текущей документацией и миграционными ограничениями.