Лабораторна робота № 10. Ядро на Rust у WASM на сторінці з відтворюваною збіркою
Мета
Навчитися компілювати Rust-код у WebAssembly за допомогою wasm-bindgen/wasm-pack,
підключати отриманий модуль до HTML-сторінки, передавати між Rust і JavaScript складні
типи даних (рядки) та налаштовувати повністю відтворювану (reproducible) збірку
фінального модуля в ізольованому Docker-контейнері.
Теоретичні відомості
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 (оцінка 3)
Зібрати ядро на Rust у формат WebAssembly (echo_number(x: i32) -> i32, повертає
x * 2), підключити до HTML-сторінки через wasm-pack build --target web і
відтворити виклик у браузері.
Рівень 2 (оцінка 4, захист)
Налаштувати передачу складних типів даних (рядків з командами) між Rust та JS:
реалізувати handle_command(cmd: &str) -> String з мінімум двома різними командами,
підключити до полів UI. На захисті — пояснити механізм маршалінгу &str/String
через лінійну пам'ять модуля.
Рівень 3 (оцінка 5, розширення)
Налаштувати повністю відтворювану збірку фінального Wasm-модуля в ізольованому
Docker-контейнері: Dockerfile з зафіксованими версіями образу і wasm-pack, що
встановлює лише тулчейн (сама збірка запускається через docker run, а не запечена
в образ RUN-кроком — інакше друга «незалежна» збірка непомітно поверне артефакт із
кешу першого шару), і скрипт, що запускає дві незалежні збірки та звіряє sha256sum
результатів. На захисті — продемонструвати однаковий хеш.
Розібраний приклад
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 sid-core-build .
rm -rf out1 out2 target
docker run --rm -v "$(pwd):/src" sid-core-build \
wasm-pack build --target web --release --out-dir out1
rm -rf target
docker run --rm -v "$(pwd):/src" sid-core-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 двох збірок.