Выражение альтернатив с помощью перечислений и match

RustBeginner
Практиковаться сейчас

Введение

Структура объединяет поля, которые всегда относятся друг к другу. Перечисление (enum) описывает значение, которое может иметь один из нескольких вариантов, причём каждый вариант может содержать свои данные. Выражение match в Rust позволяет явно обработать каждый из этих вариантов.

В этой лабораторной работе вы завершите определение перечисления сообщений, извлечёте координаты из одного из вариантов и расширите модель новым вариантом. Компилятор специально покажет, почему перед запуском обработчика сообщений необходимо охватить все возможные варианты.

Определите варианты с данными

На этом шаге вы определите три варианта одного типа Message.

Перейдите в подготовленный проект и откройте исходный файл:

cd /home/labex/project/message-processor
nano src/main.rs

Объявление перечисления содержит варианты внутри фигурных скобок. Код создаёт вариант, указывая имя перечисления, :: и имя варианта. Разные варианты одного перечисления могут содержать данные разных типов.

Замените комментарий Step 1 следующим кодом:

    Text(String),
    Move { x: i32, y: i32 },
    Stop,

Text(String) содержит одно безымянное значение. Такой вариант Rust называет кортежеподобным вариантом. Move { x, y } содержит именованные поля, поэтому это структуроподобный вариант. Stop не содержит дополнительных данных и называется вариантом-единицей. Сначала важнее запомнить различие: «одно безымянное значение, именованные поля или отсутствие данных». Формальные названия можно выучить позже. Несмотря на различия в форме, каждое созданное значение имеет один тип — Message.

Сохраните файл с помощью Ctrl+O, нажмите Enter, затем выйдите из nano с помощью Ctrl+X. Проверьте и запустите подготовленный обработчик:

cargo check
cargo run --quiet

Вывод должен быть таким:

Text: Maintenance at 9
Move received
Stop requested

Подготовленный match выбирает одну ветвь для каждого варианта. Каждая ветвь имеет форму pattern => behavior, а короткие ветви разделяются запятыми. Шаблон Text(text) присваивает имя содержащейся строке. В ветви Move также задаются имена x и y, после чего они временно объединяются в (x, y). Скобки здесь создают кортеж из двух значений, который используется только как подготовленная заготовка; изучение кортежей не входит в цель этой лабораторной работы. Имя _coordinates начинается с _, поэтому Rust считает намеренное неиспользование этого временного значения. Благодаря этому первый запуск не выдаёт предупреждений. На следующем шаге вы замените эту заготовку и выведете обе координаты.

Используйте деструктурированные данные варианта

На этом шаге вы измените ветвь Move в match, чтобы координаты, извлечённые шаблоном, появились в выводе.

Шаблоны могут извлекать части варианта, которому они соответствуют. В структуроподобном варианте запись имён полей создаёт привязки, которые доступны выражению внутри ветви.

Откройте исходный файл:

nano src/main.rs

Замените всю ветвь Move следующим кодом:

        Message::Move { x, y } => println!("Move to ({x}, {y})"),

Когда приходит сообщение Move, шаблон связывает сохранённые поля с локальными именами x и y. Выражение печати использует эти имена; отдельный доступ к полям или преобразование типа не требуется.

Сохраните файл и выйдите из редактора, затем выполните:

cargo run --quiet

Теперь средняя строка должна выглядеть так:

Move to (3, -2)

Отрицательное значение y подтверждает, что ветвь использовала данные фактического варианта, а не выводила фиксированные координаты.

Расширьте исчерпывающий match

На этом шаге вы добавите сообщение Pause, увидите проверку исчерпывающего сопоставления компилятором, а затем обработаете новый вариант.

match должен охватывать каждое возможное значение перечисления. Поэтому после добавления нового варианта компилятор указывает на каждый match, в котором нужно принять решение для этого варианта.

Откройте файл и добавьте следующий вариант сразу после Stop:

nano src/main.rs
    Pause(u32),

Сохраните файл и выйдите из редактора, затем проверьте программу. Ошибка на этом этапе ожидаема:

cargo check

Найдите основную часть диагностического сообщения:

error[E0004]: non-exhaustive patterns: `Message::Pause(_)` not covered

Компилятор указывает на match message, потому что перечисление теперь может содержать значение Pause, но ни одна ветвь не объясняет, что с ним делать.

Снова откройте исходный файл. Добавьте эту ветвь ниже ветви Stop:

        Message::Pause(seconds) => println!("Pause for {seconds} seconds"),

Затем удалите начальные // из подготовленной строки process(Message::Pause(5)); в функции main. Шаблон присваивает содержащемуся значению типа u32 локальное имя seconds.

Сохраните файл и выйдите из редактора, затем выполните проверку и запуск:

cargo check
cargo run --quiet

Полный вывод должен быть таким:

Text: Maintenance at 9
Move to (3, -2)
Stop requested
Pause for 5 seconds

Успешная проверка подтверждает, что match снова является исчерпывающим, а четвёртая строка показывает, что новая ветвь извлекла длительность паузы из варианта Pause.

Итоги

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