Простого if
/else
часто недостаточно, потому что нужно проверить больше, чем
два возможных варианта. Да и к тому же условия в else
часто становятся очень
сложными. Как же решить эту проблему?
В Rust есть ключевое слово match
, позволяющее заменить группы операторов
if
/else
чем-то более удобным. Смотрите:
let x = 5; match x { 1 => println!("один"), 2 => println!("два"), 3 => println!("три"), 4 => println!("четыре"), 5 => println!("пять"), _ => println!("что-то ещё"), }
match
принимает выражение и выбирает одну из ветвей исполнения согласно его
значению. Каждая ветвь имеет форму значение => выражение
. Выражение ветви
вычисляется, когда значение данной ветви совпадает со значением, принятым
оператором match
(в данном случае, x
). Эта конструкция называется match
(сопоставление), потому что она выполняет сопоставление значения неким
«шаблонам». Глава «Шаблоны» описывает все шаблоны, которые можно
использовать в match
.
Так в чём же преимущества данной конструкции? Их несколько. Во-первых, ветви
match
проверяются на полноту. Видите последнюю ветвь, со знаком
подчёркивания (_
)? Если мы удалим её, Rust выдаст ошибку:
error: non-exhaustive patterns: `_` not covered
Другими словами, компилятор сообщает нам, что мы забыли сопоставить какие-то
значения. Поскольку x
— это целое число, оно может принимать разные значения —
например, 6
. Однако, если мы убираем ветвь _
, ни одна ветвь не совпадёт,
поэтому такой код не скомпилируется. _
— это «совпадение с любым значением».
Если ни одна другая ветвь не совпала, совпадёт ветвь с _
. Поскольку в примере
выше есть ветвь с _
, мы покрываем всё множество значений x
, и наша программа
скомпилируется.
match
также является выражением. Это значит, что мы можем использовать его в
правой части оператора let
или непосредственно как выражение:
let x = 5; let numer = match x { 1 => "one", 2 => "two", 3 => "three", 4 => "four", 5 => "five", _ => "something else", };
Иногда с помощью match
можно удобно преобразовать значения одного типа в
другой.
Другой полезный способ использования match
— обработка возможных вариантов
перечисления:
enum Message { Quit, ChangeColor(i32, i32, i32), Move { x: i32, y: i32 }, Write(String), } fn quit() { /* ... */ } fn change_color(r: i32, g: i32, b: i32) { /* ... */ } fn move_cursor(x: i32, y: i32) { /* ... */ } fn process_message(msg: Message) { match msg { Message::Quit => quit(), Message::ChangeColor(r, g, b) => change_color(r, g, b), Message::Move { x: x, y: y } => move_cursor(x, y), Message::Write(s) => println!("{}", s), }; }
Как обычно, компилятор Rust проверяет полноту, поэтому в match
должна быть
ветвь для каждого варианта перечисления. Если какой-то вариант отсутствует,
программа не скомпилируется и вам придётся использовать _
.
Здесь мы не можем использовать обычный if
вместо match
, в отличие от кода,
который мы видели раньше. Но мы могли бы использовать if let
— его
можно воспринимать как сокращённую форму записи match
.