Сопоставление с образцом: match

match — мощный оператор выбора по образцу, который компилятор заставляет делать исчерпывающим.

match сопоставляет значение с набором образцов и выполняет ветку первого совпавшего; компилятор требует, чтобы были покрыты все возможные случаи.

Базовый match

match сравнивает значение по очереди с образцами и выполняет первую подходящую ветку. Это как switch из других языков, но гораздо умнее.

fn main() {
    let n = 3;
    let name = match n {
        1 => "один",
        2 => "два",
        3 => "три",
        _ => "много", // _ — образец «всё остальное»
    };
    println!("{name}");
}

Вывод:

три

match — это выражение: он возвращает значение, которое можно сразу присвоить переменной. Подчёркивание _ ловит все непокрытые случаи.

Исчерпывающность — суперсила match

Ключевое отличие от switch: Rust требует, чтобы match покрывал все возможные значения. Забыли вариант перечисления — код не скомпилируется. Это бесценно: добавили новый вариант в enum — компилятор перечислит все места, где его надо обработать.

enum Status {
    Active,
    Paused,
    Stopped,
}

fn label(s: Status) -> &'static str {
    match s {
        Status::Active => "работает",
        Status::Paused => "на паузе",
        Status::Stopped => "остановлен",
        // забыли ветку — ОШИБКА компиляции: pattern not covered
    }
}

fn main() {
    println!("{}", label(Status::Paused));
}

Вывод:

на паузе

Извлечение данных из образца

match умеет распаковывать данные прямо в ветке. Это идеально сочетается с перечислениями, несущими данные, и с Option.

fn main() {
    let maybe = Some(42);
    match maybe {
        Some(x) => println!("значение: {x}"), // x связывается с содержимым
        None => println!("пусто"),
    }
}

Вывод:

значение: 42

Диапазоны и несколько образцов

В одной ветке можно перечислить несколько образцов через | или задать диапазон через ..=.

fn main() {
    let score = 75;
    let grade = match score {
        90..=100 => "отлично",
        70..=89 => "хорошо",
        50..=69 => "удовлетворительно",
        _ => "плохо",
    };
    println!("{grade}");
}

Вывод:

хорошо

Охранные условия

К образцу можно добавить дополнительное условие через if — это охранное выражение (guard).

fn main() {
    let pair = (0, -5);
    let msg = match pair {
        (0, y) if y < 0 => "на оси Y ниже нуля",
        (0, _) => "на оси Y",
        (x, 0) if x > 0 => "на оси X справа",
        _ => "где-то ещё",
    };
    println!("{msg}");
}

Вывод:

на оси Y ниже нуля

Итог

  • match — выражение выбора по образцу; _ ловит остальные случаи.
  • Компилятор требует исчерпывающности: все варианты должны быть покрыты.
  • Образцы распаковывают данные, поддерживают диапазоны ..=, альтернативы | и охранные if.
Проверьте себя
1. Что значит «исчерпывающность» match в Rust?
Amatch может пропускать случаи
BКомпилятор требует покрыть все возможные варианты значения
Cmatch работает только с числами
DНужен только один вариант
2. Что делает образец _ в match?
AОбъявляет переменную
BЛовит все случаи, не покрытые предыдущими ветками
CЭто ошибка
DЗавершает программу
3. Может ли match извлекать данные из варианта, например Some(x)?
AНет
BДа, образец связывает внутренние данные с переменной прямо в ветке
CТолько для чисел
DТолько через индекс
Поддержать проект