Лабораторна робота № 10. Ядро на Rust у WASM на сторінці з відтворюваною збіркою
Мета
Навчитися компілювати Rust-код у WebAssembly за допомогою wasm-bindgen/wasm-pack,
підключати отриманий модуль до HTML-сторінки, передавати між Rust і JavaScript складні
типи даних (рядки) та налаштовувати повністю відтворювану (reproducible) збірку
фінального модуля в ізольованому Docker-контейнері.
📚Теоретичні відомості280 слів · ~2 хв
wasm-bindgen, цільwasm32-unknown-unknown, конвеєрwasm-pack, ціна перетину межі host ↔ модуль і відтворювані збірки викладені на лекції 10 того самого тижня — вона ж замикає лінію курсу. Розділ нижче наведено як довідку до фінальної збірки, а не як первинне викладення матеріалу.
wasm-bindgen — інструмент, що генерує парний код по обидва боки межі
Rust↔JavaScript: у самому .wasm-модулі та у супровідному .js-файлі («glue code»).
Функція, позначена атрибутом #[wasm_bindgen], стає викликаною напряму з JavaScript,
ніби це звичайна JS-функція — усю роботу з передачею значень через межу мов бере на
себе згенерований код.
Прості числові типи (i32, f64) відповідають типам значень WASM напряму й
передаються без копіювання. Складніші типи, зокрема &str і String, вимагають
маршалінгу: рядок Rust живе у лінійній пам'яті WASM-модуля (тема лабораторної № 8),
а JavaScript отримує пару «вказівник + довжина» і сам декодує байти UTF-8 у JS-рядок
(через TextDecoder у згенерованому glue-коді) або навпаки — кодує JS-рядок у байти
лінійної пам'яті перед викликом (TextEncoder).
wasm-pack — конвеєр збірки, що об'єднує виклик
cargo build --target wasm32-unknown-unknown, запуск wasm-bindgen CLI та пакування
результату в теку pkg/ з .wasm-бінарником, .js-обгорткою та типами .d.ts.
Прапорець --target web готує результат для прямого підключення через
<script type="module"> без збирача.
Відтворювана збірка (reproducible build) — властивість, за якої той самий
вихідний код, зібраний двічі (навіть на різних машинах), дає побайтово ідентичний
результат. Це досягається фіксацією версій усіх інструментів (компілятора Rust,
wasm-bindgen CLI) і залежностей (Cargo.lock) та збіркою в ізольованому
середовищі — Docker-контейнері з конкретним тегом базового образу (не latest),
щоб виключити вплив локального оточення розробника. Для системного й безпекового ПЗ
це дозволяє довести відповідність опублікованого бінарника опублікованому вихідному
коду.
Обладнання та програмне забезпечення
- Codespaces / devcontainer курсу з Rust,
rustup target add wasm32-unknown-unknown,wasm-pack - Docker (для рівня 3 — ізольована відтворювана збірка)
- Node.js 20+ (локальний HTTP-сервер, за потреби)
- Будь-який сучасний браузер з DevTools
Порядок виконання
- Ініціалізувати Cargo-проєкт бібліотечного типу (
cargo new --lib sid_core), додати залежністьwasm-bindgenза специфікацієюstarter/README.md. Очікуваний результат: проєкт зcrate-type = ["cdylib"]і точно зафіксованою версієюwasm-bindgen. - Написати мінімальну функцію рівня 1 з атрибутом
#[wasm_bindgen], зібрати:wasm-pack build --target web. Очікуваний результат: текаpkg/зsid_core.jsіsid_core_bg.wasm, без помилок збірки. - Підключити результат (
pkg/) доindex.html(шаблон надається вstarter/), відкрити через локальний HTTP-сервер і перевірити виклик у DevTools. Очікуваний результат: сторінка виводить коректний результатecho_number(21). - Для рівня 2 — реалізувати функцію, що приймає рядок з командою (
&str) і повертає рядок-відповідь (String), підключити до UI-елемента на сторінці. - Для рівня 3 — написати
Dockerfileз фіксованою версією образу Rust, що виконуєwasm-pack buildвсередині контейнера; переконатися, що дві послідовні збірки дають ідентичний за хешем (sha256sum).wasm-файл. - Запустити локальний скрипт перевірки з
autograder/, переконатися, що всі перевірки рівня 1 проходять, перш ніж здавати роботу.
Два репозиторії. Рівень 1 виконується в репозиторії завдання — його створює Classroom 50 на прийняття, окремий на кожну роботу, і саме там працюють автотести (здача тегом
submit/labNN). Рівні 2 і 3 виконуються в наскрізному репозиторіїvtfk-sid, який студент створює один раз у роботі № 1 і веде до кінця семестру; вони оцінюються на захисті, не автотестами. Шляхи видуcore/...у тексті нижче — це шляхи вvtfk-sid.Тег ставить скрипт здачі, а не студент. У робочому просторі — Ctrl+Shift+B (меню
Terminal → Run Build Task), або те саме з терміналу:bash .devcontainer/submit.sh. Скрипт комітить незбережене, показує, який тег створить, і чекає підтвердження; відмова нічого не здає. Ручніgit tag/git pushлишаються запасним шляхом — вони працюють і поза Codespaces.
Рівень 1 (оцінка 3)
Зібрати ядро на Rust у формат WebAssembly (echo_number(x: i32) -> i32, повертає
x * 2), підключити до HTML-сторінки через wasm-pack build --target web і
відтворити виклик у браузері. Рівень 1 виконується в каталозі поточної роботи
(sid_core/) на новому crate і не залежить від результатів попередніх робіт.
Рівень 2 (оцінка 4, захист)
Налаштувати передачу складних типів даних (рядків з командами) між Rust та JS:
реалізувати handle_command(cmd: &str) -> String з мінімум двома різними командами,
підключити до полів UI. Опублікувати сторінку на GitHub Pages свого репозиторію
роботи, з обличчям С.І.Д. поруч із полем вводу. На захисті — відкрити власну публічну
адресу (не localhost) і пояснити механізм маршалінгу &str/String через лінійну
пам'ять модуля.
Публікація — не оформлення, а частина результату: wasm-pack build --target web дає три
файли (.wasm, JS-обгортку, типи), і сторінка працює лише тоді, коли всі три лежать поруч
і віддаються по HTTP із правильним MIME-типом. Відкрита з file:// вона не запуститься —
WebAssembly.instantiateStreaming цього не дозволяє. Опублікована сторінка доступна за
адресою будь-кому, хоча сам репозиторій роботи приватний (ADR 029).
Два рівноцінні варіанти виконання (оцінка однакова):
- A — на власному наскрізному артефакті: crate робіт № 4–7 (
core/sidkit) підключається як залежність шляхом (уvtfk-sid—sidkit = { path = "../sidkit" }), іhandle_commandне формує відповіді сам, а маршрутизує їх у модуліintelтаguard. У браузері виконується той самий код, що розроблявся з роботи № 4 — це і є розв'язка наскрізної лінії курсу. - B — на референсному знімку: береться
vtfk-sid-reference@after-lab09, або відповіді формуються безпосередньо вlib.rs.
Допуск до захисту. Перед захистом рівня 2 треба залишити три предметні зауваги до роботи № 9 іншого студента — в issue або коментарі до комміта його репозиторію завдання. Кого рецензувати: студента, чий номер у списку групи на 10 більший за власний (по колу — номер цієї роботи задає зсув, тому пари щоразу різні). Заувага предметна, якщо називає конкретний рядок, команду або крок і пояснює, у чому проблема; загальні оцінки («усе правильно», «гарний код») не враховуються. Це елемент ЗК7 — здатності працювати в команді: рівень 1 доводить, що код збирається, рецензія — що студент уміє його читати.
Рівень 3 (оцінка 5, розширення)
Налаштувати повністю відтворювану збірку фінального Wasm-модуля в ізольованому
Docker-контейнері: Dockerfile з зафіксованими версіями образу і wasm-pack, що
встановлює лише тулчейн (сама збірка запускається через docker run, а не запечена
в образ RUN-кроком — інакше друга «незалежна» збірка непомітно поверне артефакт із
кешу першого шару), і скрипт, що запускає дві незалежні збірки та звіряє sha256sum
результатів. На захисті — продемонструвати однаковий хеш.
Ті самі два варіанти: A — за основу береться власний core/capsule/Dockerfile робіт
№ 2–3 і доводиться до відтворюваності; B — новий Dockerfile за шаблоном. Два збіглі
хеші обов'язкові в обох варіантах.
Cargo.toml і src/lib.rs рівня 1:
[package]
name = "sid_core"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "=0.2.92"
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn echo_number(x: i32) -> i32 {
x * 2
}
wasm-pack build --target web
python3 -m http.server 8080
Маршалінг рядків рівня 2:
#[wasm_bindgen]
pub fn handle_command(cmd: &str) -> String {
match cmd {
"status" => String::from("ядро активне"),
_ => String::from("невідома команда"),
}
}
Відтворювана збірка рівня 3 — Dockerfile (лише фіксований тулчейн; збірку виконує
docker run, а не RUN у образі, щоб кожен запуск справді компілював код заново):
FROM rust:1.79-slim
RUN rustup target add wasm32-unknown-unknown
RUN cargo install wasm-pack --version 0.13.0 --locked
WORKDIR /src
Перевірка відтворюваності:
docker build -t vtfk-sid-build .
rm -rf out1 out2 target
docker run --rm -v "$(pwd):/src" vtfk-sid-build \
wasm-pack build --target web --release --out-dir out1
rm -rf target
docker run --rm -v "$(pwd):/src" vtfk-sid-build \
wasm-pack build --target web --release --out-dir out2
sha256sum out1/sid_core_bg.wasm out2/sid_core_bg.wasm
Однакові хеші підтверджують відтворюваність збірки.
- Що саме генерує
wasm-bindgenпо обидва боки межі Rust↔JavaScript? - Чому прості числові типи передаються через межу без копіювання, а рядки — ні?
- Що робить прапорець
--target webуwasm-pack build? - Що таке «відтворювана збірка» і чому недостатньо просто зафіксувати версію Rust?
- Навіщо в Dockerfile для відтворюваної збірки уникати тега
latestдля базового образу? - Як перевірити, що дві збірки дали побайтово ідентичний результат?
- Чим ризикована ситуація, коли опублікований
.wasm-бінарник не відповідає опублікованому вихідному коду? - Яка з трьох меж курсу (контейнер / володіння Rust / лінійна пам'ять WASM) задіяна в цій лабораторній і як саме?
Парні теми СРС
- Рівень 2: srs19 — Межа Rust↔JS (wasm-bindgen) і маршалінг даних
- Рівень 3: srs20 — Відтворювані збірки і ланцюг постачання (supply chain)
Критерії оцінювання та форма звіту
Здається тег submit/lab10 з Cargo-проєктом (Cargo.toml, src/lib.rs,
Cargo.lock), index.html, і для рівня 3 — Dockerfile. Автотести рівня 1 (див.
autograder/README.md) перевіряють: проєкт компілюється у WASM без помилок,
згенерований pkg/ містить очікувану експортовану функцію з правильною сигнатурою,
виклик з Node.js дає коректний результат. Рівні 2 і 3 захищаються усно: студент
показує передачу рядка через DevTools console і — для рівня 3 — демонструє однаковий
хеш .wasm-файлу після двох незалежних збірок у контейнері. Звіт додає скріншоти
консолі з викликом функції, UI з обробленою командою і (рівень 3) виведення
sha256sum двох збірок.