# Rust

## Що це таке

Rust — це мова програмування для роботи, яку традиційно виконують на C і C++, де
програма сама керує своєю пам'яттю і часто компонується з програмами, написаними
іншими мовами. Компілятор заздалегідь перетворює вихідний код на машинний.
Версію 1.0 оголосили 15 травня 2015 року як мову, що поєднує низькорівневий
контроль над продуктивністю з гарантіями безпечної роботи з пам'яттю й досягає
цього без збирача сміття і без середовища виконання, тож бібліотека на Rust може
стати на місце бібліотеки на C.

Пам'яттю керує володіння — набір правил, які перевіряє компілятор. Кожне
значення має одного власника водночас, і коли власник виходить з області
видимості, значення звільняється разом зі своєю пам'яттю в момент, визначений
під час компіляції, а не тоді, коли збирач сміття наступного разу зазирне. Код,
якому потрібне значення без передачі володіння, позичає посилання, а правила
позичання дозволяють у кожну мить або одне змінне посилання, або будь-яку
кількість незмінних і вимагають, щоб кожне посилання було дійсним. Програма, що
порушує правило, не компілюється. Ці самі правила виключають перегони даних, тож
два потоки, що без синхронізації пишуть у ті самі дані, — це помилка компіляції,
а не збій, який з'являється лише зрідка.

У Rust немає винятків. Функція, що може завершитися помилкою передбачуваним
чином, повертає `Result`, який містить або значення, або помилку, і в багатьох
випадках компілятор вимагає, щоб код, який її викликає, обробив помилку, перш
ніж програма збереться; помилка, після якої програма не може відновитися, як-от
читання за межами масиву, натомість іде через макрос `panic!`.

Статичний аналіз консервативний, тож компілятор відкидає деякі програми, які
були б коректними, а деяку роботу, як-от керування обладнанням чи виклик іншої
мови, перевірити неможливо взагалі. Для таких випадків є `unsafe`, що відкриває
п'ять додаткових можливостей, серед них розіменування сирого вказівника і виклик
небезпечної функції, а такою типово є будь-яка функція, оголошена з C. Він не
вимикає перевірку позичань; він позначає рядки, де за коректну роботу з пам'яттю
ручається програміст, а не компілятор, тож помилка пам'яті може жити лише в
блоці `unsafe`. Порада книги Rust — тримати ці блоки малими й загортати їх у
безпечний інтерфейс, і саме так побудовано частини стандартної бібліотеки.

Новий компілятор виходить кожні шість тижнів. Зміни, що зламали б наявний код,
чекають на редакцію (edition), яка з'являється приблизно раз на три роки. Їх
чотири, 2015, 2018, 2021 і 2024, і найновіша стала стабільною 20 лютого 2025
року разом із Rust 1.85.0. Кожен пакет указує свою редакцію в `Cargo.toml`,
кожен компілятор збирає всі редакції, що вийшли до нього, а пакети різних
редакцій компонуються разом, тож проєкт переходить на нову тоді, коли сам
вирішить. Cargo, менеджер залежностей і засіб збирання, входить до складу мови,
rustfmt тримає стиль коду однаковим, а сторонні бібліотеки надходять як пакети,
що називаються крейтами.

## Чому це важливо

У C і C++ вказівник, використаний після звільнення його пам'яті, або два потоки,
що пишуть ті самі дані, компілюються без жодних заперечень і дають збій пізніше,
зрідка й часто далеко від причини. Rust перетворює такі програми на помилки
компіляції, а оскільки перевірки відбуваються під час збирання, готова програма
нічого за них не платить.

Без збирача сміття і без середовища виконання, яке треба тягти за собою, код на
Rust може лежати під програмами іншими мовами. Усе, що вміє викликати C, може
викликати ядро на Rust через C-інтерфейс, тож одна добре протестована реалізація
може обслуговувати клієнтів на кількох платформах. Книга Rust серед застосувань
мови в продакшні називає інструменти командного рядка, вебсервіси, вбудовані
пристрої та значні частини браузера Firefox.

Ціну платять наперед. Код треба будувати так, щоб компілятор бачив, що він
безпечний, а це часто означає вирішити, хто володіє значенням, замість вільно
передавати посилання, і книга сама визнає, що до володіння треба звикнути. Коли
компілятор відкидає програму, якої не може перевірити, відповіддю зазвичай стає
інша структура, а не обхід перевірки.

Стабільність — друга половина угоди. Від версії 1.0 зміни, що ламають наявний
код, здебільшого виключено, тож така зміна надходить лише через редакцію, яку
пакет обирає сам.

## Як це виглядає на практиці

У портфоліо Rust — це ядро під
[нативним клієнтом обміну повідомленнями для macOS](/uk/portfolio/built-a-native-macos-messaging-client-in-swift-6-154/):
добре протестоване ядро протоколу написано на Rust, воно надає C-інтерфейс, а
клієнт, 11 141 рядок Swift у 53 файлах, компонує його як статичний архів,
зібраний із зафіксованої ревізії основного репозиторію. Фіксація важлива, бо
C-інтерфейс — це контракт: ядро, яке змінюється, змінює контракт, не змінюючи
жодного рядка Swift. Для всього, що повертає C-інтерфейс, діє своє правило
володіння, і помилка в одному з них дає витік або аварійне завершення без
жодного попередження компілятора між ними, тож тонка обгортка володіє кожним
вказівником і кожним рядком, що перетинає межу, і жоден шар над нею не бачить
сирого вказівника.

Компонування цього ядра із застосунком означає постачання всього, від чого воно
залежить, і саме цьому присвячено
[перевірку ліцензій 644 крейтів Rust](/uk/portfolio/audited-644-rust-crates-for-licence-compatibility-163/).
Кожен крейт декларує свою ліцензію як вираз, і під час кожної збірки перевірка
звіряє кожен вираз у повному графі залежностей зі списком дозволених умов,
читаючи OR як вибір між ліцензіями, а AND — як обов'язок дотримуватися обох.
Усе, що не збігається, зупиняє збірку з помилкою. Оскільки перевірка, яка ніколи
не спрацьовувала, має такий самий вигляд, як і та, що спрацювати не може, її
двічі навмисно змусили спрацювати: раз — переписавши ліцензію одного крейта на
умову, яку список відкидає, і раз — змістивши зафіксовану ревізію ядра без
перегенерації. Вона читає лише задекларовані метадані й не є юридичною
консультацією.

Власного коду на Rust цей сайт не має; з мовою він стикається опосередковано,
через Ruff, що написаний на Rust і перевіряє кожен скрипт Python, якого
торкається коміт.

## Де виникають проблеми

Здебільшого клопіт із Rust виникає на краях того, що перевіряє компілятор: в
арифметиці оптимізованої збірки, на межі з іншою мовою і в коді, що надходить
разом із залежністю.

**У релізній збірці арифметика переповнюється по колу.** Лічильник, що під час
тестування панікував через переповнення, у продакшні тихо стає малим числом. У
налагоджувальних збірках Rust перевіряє цілочисельну арифметику на переповнення
й панікує; збірка з `--release` цю перевірку пропускає, і значення переходить по
колу, тож 256 у `u8` стає 0, а книга вважає помилкою покладатися на таку
поведінку. Де значення може переповнитися, оберіть поведінку явно: `checked_*`
повертає `None`, `saturating_*` зупиняється на межі, а `wrapping_*` переходить
по колу навмисно.

```rust
fn add_stock(on_hand: u8, delivered: u8) -> Option<u8> {
    // None when the sum does not fit, in debug and release alike
    on_hand.checked_add(delivered)
}
```

**Помилки пам'яті повертаються на межі з C.** Програма, чия частина на Rust
збирається без жодного попередження, має витоки пам'яті або аварійно
завершується всередині зовнішнього виклику. Функції, оголошені в блоці `extern`,
небезпечно викликати, бо інша мова не забезпечує жодного з правил Rust, тож те,
хто звільняє кожен вказівник, — це домовленість, якої компілятор не бачить.
Призначте одну обгортку відповідальною за кожен вказівник і рядок, що перетинає
межу, запишіть кожне правило володіння поруч із викликом і відкривайте шарам
вище лише безпечний інтерфейс.

**Паніка доходить до C, і процес аварійно завершується.** Функція на Rust,
викликана з C, панікує, і замість значення помилки зупиняється вся програма.
Починаючи з Rust 1.81, що вийшов 5 вересня 2024 року, паніка, яка намагається
вийти з функції, оголошеної зі звичайним інтерфейсом `"C"`, перериває процес.
Перетворюйте збої на значення, що повертаються, ще до межі, а функцію з
`"C-unwind"` оголошуйте лише там, де код на іншому боці очікує розмотування
стека.

**Рутинне оновлення змінює ліцензію всієї збірки.** Підвищення версії на три
рівні нижче приносить крейт, ліцензію якого продукт прийняти не може, і ніщо в
збірці про це не повідомляє. Кожен крейт декларує власну ліцензію, і
зобов'язання накопичуються по всьому графу. Звіряйте вираз кожного крейта зі
списком дозволених умов під час кожної збірки, зупиняйте збірку на всьому, що не
збігається, і один раз навмисно зламайте перевірку, щоб довести, що вона
спрацьовує.

## Що почитати далі

- [The Rust Programming Language](https://doc.rust-lang.org/book/) — офіційна
  книга, від встановлення через володіння й обробку помилок до небезпечного
  коду.
- [What Is Ownership?](https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html)
  — три правила володіння і те, як пам'ять звільняється без збирача сміття.
- [Unsafe Rust](https://doc.rust-lang.org/book/ch20-01-unsafe-rust.html) — п'ять
  небезпечних можливостей, сирі вказівники й виклик C через `extern`.
- [The Manifest Format](https://doc.rust-lang.org/cargo/reference/manifest.html)
  — поля `edition`, `rust-version` і `license` у `Cargo.toml`.
- [Announcing Rust 1.0](https://blog.rust-lang.org/2015/05/15/Rust-1.0/) —
  початкові цілі й обіцянка стабільності, що почалася з 1.0.

<https://engineer.company/uk/glossary/rust/>
