Назад
Rust і «Parse, don't validate»: навіщо перевірку міняти на тип
SiTech AI Team3 წთ. საკითხავი

Rust і «Parse, don't validate»: навіщо перевірку міняти на тип

Елі Бендерскі переглядає ідею «Parse, don't validate» для Rust: дані треба не просто перевіряти, а перетворювати на тип, який робить порушення гарантованої властивості неможливим.

Елі Бендерскі опублікував на своєму блозі есе, яке повертається до ідеї зі статті Алексіс Кінг 2019 року. Її текст, написаний для Haskell, доводить: код має не просто перевірити умову й потім довіряти їй, а перетворити дані на такий тип, для якого порушення гарантованої властивості неможливе. Бендерскі застосовує цей погляд до Rust і шукає повчальні приклади у стандартній бібліотеці мови та у відомих проєктах.

Вектор, який не може бути порожнім

Відправна точка — звичайний Vec та його метод first(), який повертає необов'язкове посилання, бо вектор може не містити жодного елемента. У прикладі Бендерскі функція читає каталоги конфігурації зі змінної CONFIG_DIRS і відкидає порожній результат, але викликач усе одно мусить написати гілку для випадку, який ніколи не настане. Автор порівнює такі інваріанти з бомбою з годинниковим механізмом: компілятор їх не перевіряє, тож згодом вони можуть тихо зламатися. Рішення — окремий тип, наприклад NonEmpty з крейта nonempty, який не має конструктора для порожнього значення, а first() повертає звичайне посилання. Повернути перевірений Vec — це валідація, а повернути NonEmpty — це парсинг, якщо розуміти парсинг широко: як перетворення даних з одного формату на інший.

Конвеєри, шляхи та поступове уточнення

Далі есе виходить за межі навчального коду. У проєкті posixutils-rs, переписуванні базових утиліт POSIX на Rust, команди конвеєра оболонки зберігаються як NonEmpty, тому розібраний конвеєр завжди містить хоча б одну команду, а парсер повертає None лише тоді, коли запускати нічого. rust-analyzer має окремий тип для абсолютного шляху до файлу: перетворення не вдається, якщо шлях не абсолютний, і подальший код покладається на тип, а не перевіряє умову знову. Бендерскі називає це поступовим парсингом: шлях спершу доводиться як чинний UTF-8 за допомогою крейта camino, і лише потім — як абсолютний.

Ненульові числа та JSON

У стандартній бібліотеці Rust є узагальнений тип NonZero для беззнакових чисел, про які відомо, що вони не дорівнюють нулю. Функція available_parallelism у разі успіху повертає саме таке значення, а ділення з дільником NonZero задокументовано як операція, що не може панікувати. Є й додаткова перевага: нуль — недопустима бітова комбінація для цього типу, тож необов'язковий NonZero займає рівно стільки ж пам'яті, скільки звичайне ціле число. Та сама ідея працює під час розбору JSON: із serde структуру конфігурації можна описати так, що кількість воркерів ненульова, а режим — перелічення з двох значень; десеріалізація одночасно перевіряє типи полів, дозволені значення та заборону нуля. У динамічних мовах це значно більше ручної роботи: json.loads у Python повертає словник і залишає перевірку викликачеві, а бібліотеки на кшталт Pydantic наближаються до підходу Rust, але використовуються не всюди.

SSiTech

SiTech — веброзробка з підтримкою AI

Створюємо швидкі та сучасні сайти й інтегруємо AI у бізнес-процеси. Маєте проєкт чи запитання? Із задоволенням допоможемо.