Курс СРС SRS11
до ЛР6 рівень 2 → «4»↔ Лабораторна 6↔ Лекція 6⌨️ команди 2
Самостійна робота · ~2.5 год

Алгебраїчні типи даних (enum) і вичерпне зіставлення зі зразком (match)

🌌 Мова опису ворожих вузлів — Периметр «ТехНови» описується enum-станами вузлів — і С.І.Д. з видимим захватом показує улюблений трюк: якщо з'явиться новий тип вузла, а десь у коді про нього забудуть, компілятор сам укаже на кожне місце, де треба дописати обробку, замість того, щоб мовчки пропустити його в рантаймі.

📚 ТЕОРЕТИЧНІ ВІДОМОСТІ

enum у Rust — не просто список констант

У багатьох мовах enum — це просто набір іменованих цілочисельних констант. У Rust enum — це алгебраїчний тип даних (algebraic data type, ADT), де кожен варіант може нести власні, різні за формою дані. enum NodeStatus { Active, Suspicious(u32), Compromised { by: String, at: u64 } } описує три принципово різні форми стану: без даних, з одним неозначеним значенням і зі структурою з іменованими полями — все в одному типі. Це радикально відрізняється від enum-int у C чи Java: тут варіант і його дані нероздільні, і компілятор знає точний набір можливих форм значення цього типу — не більше й не менше, ніж перелічено.

Основний спосіб «розпакувати» enum і дістатися даних усередині варіанта — конструкція match. Ключова гарантія Rust: match над enum повинен бути вичерпним (exhaustive) — тобто охоплювати абсолютно всі можливі варіанти, інакше компіляція не відбудеться з помилкою non-exhaustive patterns (або зазначить конкретно, які варіанти пропущено). Розробнику не потрібно пам'ятати весь список варіантів напам'ять і сподіватися, що він нічого не забув, — компілятор зробить цю перевірку за нього автоматично й безкоштовно для кожного match у кодовій базі.

Саме тут проявляється ключова педагогічна й інженерна перевага цього підходу порівняно з, наприклад, if/else ланцюжком по цілочисельному коду статусу: коли до enum додається новий варіант (скажімо, Quarantined), кожен match у всій кодовій базі, який раніше вичерпно обробляв старий набір варіантів, миттєво перестає компілюватися — компілятор буквально вказує на кожне місце в коді, де потрібно дописати обробку нового випадку. Це перетворює «забув обробити новий стан десь у величезному проєкті» — типову причину прихованих багів у динамічних чи слабко типізованих системах — на помилку компіляції, яку неможливо не помітити.

match підтримує потужні патерни, що виходять за межі простого перерахування варіантів: деструктуризацію вкладених даних просто в патерні (Compromised { by, at } if at > threshold => ... — з умовою-guard), зв'язування частини значення через @ (Suspicious(n @ 100..=200) => ...), і навмисний «викид» вичерпності через _ => ... (яка варта уваги: додавання _ глушить майбутню перевірку компілятора для нових варіантів — це свідома відмова від однієї з головних переваг механізму, тому вважається практикою, якої варто уникати, якщо є спосіб перелічити варіанти явно).

Крім match, з Rust 1.65+ доступна коротша форма if let для випадку, коли цікавить лише один варіант (if let NodeStatus::Compromised { by, .. } = status { ... }), і let else для «розпакувати або вийти» (let NodeStatus::Active = status else { return; };). Обидві конструкції — не альтернативи вичерпності, а зручні скорочення для типового підмножини сценаріїв match, які самі по собі не вимагають обробки всіх варіантів, оскільки явно описують «а якщо не цей варіант — роби щось інше/просто вийди».

📖 ОПРАЦЮВАТИ

Прочитати розділи «Enums» та «The match Control Flow Construct» у The Rust Book; додати новий варіант до наявного enum і подивитися, які блоки match компілятор позначить як неповні (помилка non-exhaustive patterns).

🖥️ ТЕРМІНАЛ — СПРОБУЙ САМ
🎯 ЗАВДАННЯ
  1. Оголосити enum NodeStatus із щонайменше трьома варіантами різної форми (без даних, з одним значенням, зі структурою іменованих полів) і написати вичерпний match, що обробляє кожен варіант окремо.
  2. Додати до NodeStatus четвертий варіант і зафіксувати точний текст помилки компіляції non-exhaustive patterns у щонайменше одному раніше написаному match; виправити, додавши обробку нового варіанта.
  3. Написати match із guard-умовою (кшталт Suspicious(n) if n > 100 => ...) і показати, що та сама структура патерна без guard і з guard веде до різних гілок для того самого значення.
  4. Переписати один із match на еквівалентну конструкцію if let для випадку, коли цікавить лише один конкретний варіант, і письмово пояснити, чому тут вичерпність компілятором уже не вимагається.
  5. Навмисно додати гілку _ => {} у вичерпний match, потім додати новий варіант enum і показати, що компіляція проходить мовчки без попередження про необроблений випадок — пояснити письмово, чому це вважається антипатерном.
❓ КОНТРОЛЬНІ ПИТАННЯ
  1. Чим enum у Rust принципово відрізняється від переліку цілочисельних констант у C/Java?
  2. Що станеться під час компіляції, якщо match над enum з чотирма варіантами обробляє лише три й не має гілки _ => ...?
  3. Яку конкретну категорію багів компілятор перетворює на помилку компіляції завдяки вимозі вичерпності match?
  4. Чим match-guard (умова після if усередині гілки) відрізняється від звичайного патерна без умови?
  5. Чому додавання _ => ... до вичерпного match вважається практикою, якої варто уникати при подальшому розвитку enum?
  6. У яких випадках доцільно використати if let замість повного match, і чому це не порушує гарантій компілятора?
  7. Яку інформацію несе варіант enum виду Compromised { by: String, at: u64 } порівняно з варіантом без даних?
✅ САМОПЕРЕВІРКА

Що станеться під час компіляції, якщо в match над enum із чотирма варіантами обробити лише три й не додати гілку _ => ...?

🥚 ПАСХАЛКА
Додай до NodeStatus варіант Quarantined { block: &'static str, floor: i8 } і сконструюй його як Quarantined { block: "Б-7", floor: -3 } — компілятор чесно перелічить усі match, які про мінус третій ще не знають. С.І.Д. каже, що «ТехНова» такого варіанта ніколи не оголошувала: у їхньому коді того поверху офіційно не існує. Саме тому він і збирається туди.
⌨️ КОМАНДИ НА ЦІЙ СТОРІНЦІ 2

Кожна команда терміналу з цієї сторінки — одним реченням. Позначка нове — команда зустрічається в курсі вперше; далі вважаємо її знайомою.

cargo build
Компілює проєкт у target/debug/; --release — з оптимізаціями у target/release/.
cargo
Система збірки й менеджер пакетів Rust: створює проєкт, тягне залежності (крейти), збирає, тестує, запускає.

💡 Прочитати README прямо в терміналі: cat README.md (виведе весь файл) або less README.md (посторінково; вихід — клавіша q).
Довідка з будь-якої команди: man curl (повний посібник, вихід — q) або коротко curl --help; для підкоманд — docker run --help, cargo build --help.

← SRS10усі темиSRS12 →