☰ Конспект ← Курс
ЛЕКЦІЯ 10

RUST У WASM, МЕЖА З HOST-СЕРЕДОВИЩЕМ

Замикання лінії курсу

VTFK · Системне програмування · Модуль 3

План лекції

  • Від ручного .wat до Rust→WASM
  • wasm-bindgen: що саме генерується автоматично
  • wasm-pack: конвеєр збірки
  • Маршалінг типів через межу Rust↔JS
  • Відтворювані збірки: чому це важливо для системного ПЗ
  • Три межі курсу — підсумок

Фокус: WASM як компільований бекенд, а не мова для ручного письма

Навіщо компілювати Rust у WASM

  • У ЛР8 ми писали .wat вручну — це навчально, але непрактично для реальних програм
  • Rust перевіряє володіння й запозичення (Л5–Л7) при компіляції — гарантії для коду в браузері
  • target wasm32-unknown-unknown: той самий rustc, інша ціль компіляції замість x86-64
  • Результат — .wasm з експортами, як у Л9, але згенерованими компілятором, а не вручну

Компілятор Rust розв'язує ту саму задачу компіляції, що й gcc -S у Л8, лише для іншої машини

Ручний .wat (Л9) проти Rust → WASM (Л10)

Ручний .wat
  • Людина розставляє local.get / i32.add
  • На межі лише i32, i64, f32, f64
  • Кожен export пишеться вручну
  • Навчальна модель стекової машини
Rust → wasm32-unknown-unknown
  • rustc + LLVM генерують інструкції
  • String, struct, Vec — через wasm-bindgen
  • Експорти — з атрибута #[wasm_bindgen]
  • Гарантії володіння (Л5–Л7) у браузері

Той самий байт-код на виході; змінюється лише те, хто його пише — людина чи компілятор

Rust → WASM у цифрах

1
команда збірки
wasm-pack build --target web
32
біти адреси лінійної пам'яті
ціль wasm32-unknown-unknown
4
файли у pkg/
.wasm · .js · .d.ts · package.json
1
хеш для двох незалежних збірок
sha256sum — доказ відтворюваності

Той самий rustc, що збирав x86-64 у Модулях 1–2, — інша ціль компіляції

Ціль компіляції: триплет wasm32-unknown-unknown

ЧастинаЗначенняЩо означає
архітектураwasm3232-бітна адресація лінійної пам'яті
виробникunknownдля віртуальної машини не має сенсу
ОСunknownнемає ОС: жодного libc, жодних системних викликів
сусідня цільwasm32-wasi (wasip1/wasip2)POSIX-подібні виклики через WASI — поза браузером

rustup target add wasm32-unknown-unknown — одноразово. Курс тримається unknown-unknown, бо мета — сторінка в браузері, а не окремий бінарник

Rust-функція для WASM

rust
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn greet(name: &str) -> String {
    format!("Вітаю, {name}! Ядро на зв'язку.")
}

Атрибут #[wasm_bindgen] позначає межу з JavaScript

Що генерує #[wasm_bindgen]

Усередині .wasm (guest)
  • extern "C"-обгортки для експортованих функцій
  • Приймають і повертають лише числові дескриптори
  • Рядок живе в лінійній пам'яті: (вказівник, довжина)
У .js — glue code (host)
  • TextEncoder/TextDecoder копіює байти UTF-8
  • Звичайний виклик greet(name) ховає роботу з пам'яттю
  • Для структур — JS-класи з методом .free()

Це і є маршалінг (marshalling) — перетворення значень при перетині межі між WASM-модулем (guest) і JS-хостом (host). i32/f64 проходять напряму — вони й так типи WASM

Ціна перетину межі: числа й рядки

МЕЖА JS ↔ WASM: ЧИСЛА ЙДУТЬ, РЯДКИ КОПІЮЮТЬСЯ JS · HOST (браузер) 3.14 звичайне число greet('С.І.Д.') аргумент — JS-рядок МЕЖА напряму, без копії типи самого WASM WASM-BINDGEN · GLUE копія на виклик TextEncoder → UTF-8 у пам'ять модуля у WASM входять лише (ptr, len) результат — так само копія, потім free WASM · GUEST лінійна пам'ять f64 нативний тип WASM (ptr, len) → &str Rust читає з пам'яті числа безкоштовні, рядки — маршалінг: проєктуй API так, щоб рядки йшли рідко

i32/f64 — типи самої машини WASM; JS-рядок у модуль не входить — входить лише (ptr, len)

wasm-pack: конвеєр збірки

  • wasm-pack build --target web — одна команда: компіляція + запуск wasm-bindgen CLI + пакування
  • Результат у pkg/: .wasm бінарник, згенерований .js із класами/функціями, .d.ts типи для TypeScript
  • target web готує ES-модуль, який можна одразу підключити через <script type=module>
  • Це заміняє ручний виклик rustc + написання .wat, який ми робили в ЛР8

wasm-pack — те саме, що cargo build, тільки для цілі wasm32 з додатковою генерацією обгортки

RUST → WEBASSEMBLY → БРАУЗЕР .rs Rust код wasm32 cargo build wasm-bindgen JS-міст браузер .wasm+.js wasm-pack пакує модуль + типізований JS-обгортку для сторінки

Що лежить у pkg/ після збірки

ФайлЩо цеХто використовує
sid_core_bg.wasmсам бінарник модулябраузер через init()
sid_core.jsES-модуль: init(), greet(), класи<script type="module">
sid_core.d.tsтипи експортівTypeScript, редактор
package.jsonметадані пакетаnpm, бандлери

--target web — ES-модуль без бандлера; --target bundler — Webpack/Vite; --target nodejs — CommonJS

Виклик з HTML/JS

html
<script type="module">
  import init, { greet } from './pkg/sid_core.js';
  await init();
  document.querySelector('#btn').onclick = () => {
    console.log(greet('С.І.Д.'));
  };
</script>

init() підвантажує і інстанціює .wasm; greet — вже звичайна JS-функція

Маршалінг типів: що дешево, що дорого

Rust-типЩо передається фактичноВартість
i32, u32, f64, boolнапряму, як число WASMнуль — нативний тип
&str, String(вказівник, довжина) + копія UTF-8TextEncoder/TextDecoder
Vec<T> з числамивказівник + довжина → TypedArrayодна копія через ArrayBuffer
struct з #[wasm_bindgen]непрозорий JS-клас із вказівникомгеттери/сеттери від макроса
enum з даними, вкладені типисеріалізація (serde-wasm-bindgen, JSON)найдорожче

Правило гарячого шляху: чим простіший тип на межі, тим дешевший виклик. Порівняти з Л8: там межею була угода ABI, тут — контракт wasm-bindgen

Пам'ять через межу: хто звільняє

Rust structживе в лінійній пам'яті .wasm
#[wasm_bindgen]генерує JS-клас з вказівником
JS тримає дескрипторmemory.buffer видимий як ArrayBuffer
.free()явний виклик → Drop у Rust
забули .free()витік: купа модуля зайнята

У WASM немає збирача сміття: JS-об'єкт зникне, а пам'ять усередині модуля — ні. Жоден JS-профайлер цей витік не покаже

Живе демо: виклик Rust-функції зі сторінки

  1. wasm-pack build --target web у теці з Cargo-проєктом
  2. python3 -m http.server у теці з index.html (WASM вимагає http, не file://)
  3. Відкрити сторінку, натиснути кнопку
  4. DevTools → Console: рядок від greet('С.І.Д.')
  5. DevTools → Network: розмір .wasm поруч із розміром glue .js

Очікувано: Клік по кнопці друкує рядок, згенерований у Rust, скомпільований у WASM, викликаний із JS

Відтворювані збірки (reproducible builds)

🎯
Проблема«на моїй машині інший .wasm»: інші версії rustc/wasm-bindgen — інші байти
📦
Ізольована збіркаDocker-контейнер з фіксованими інструментами — принцип Модуля 1
📌
Фіксовані версіїCargo.lock + FROM rust:1.XX-slim — детермінований набір залежностей
🔏
Доказsha256sum двох незалежних збірок збігається — бінарник відповідає коду

Це замикає лінію курсу: контейнер (Модуль 1) знову стає інструментом гарантії, тепер для збірки WASM

Dockerfile для відтворюваної збірки

dockerfile
FROM rust:1.82-slim
RUN rustup target add wasm32-unknown-unknown
RUN cargo install wasm-pack --version 0.13.1 --locked
WORKDIR /build
COPY Cargo.toml Cargo.lock ./
COPY src ./src
RUN wasm-pack build --target web --release

Фіксовані версії образу, wasm-pack і Cargo.lock — три складові відтворюваності

Панічна поведінка Rust у WASM

  • За замовчуванням паніка в WASM-модулі може завершитись без жодного повідомлення в консолі
  • console_error_panic_hook — крейт, що перенаправляє паніку в console.error() браузера
  • Підключається один раз при старті: console_error_panic_hook::set_once()
  • Без нього діагностика падіння модуля перетворюється на здогадки за симптомами

Дрібниця, яку легко забути й довго шукати в проді

Живе демо: перевірка відтворюваності збірки

  1. docker build -t sid-core-build . — зібрати образ з Dockerfile
  2. docker run --rm -v $(pwd)/out1:/build/pkg sid-core-build
  3. docker run --rm -v $(pwd)/out2:/build/pkg sid-core-build — та сама збірка, окремий запуск
  4. sha256sum out1/*.wasm out2/*.wasm — порівняти хеші двох незалежних збірок

Очікувано: Хеші .wasm-файлів з обох запусків збігаються побайтово — доказ відтворюваності

Три межі курсу — підсумок

МежаМодульЩо ізолює / перевіряєХто перевіряє
Процес у контейнеріМ1простори імен, cgroups — те саме ядро Linuxядро (namespaces)
Безпечний доступ до пам'ятіМ2володіння й запозичення Rustкомпілятор (borrow checker)
Переносний набір інструкційМ3від x86-64 до WASM: один алгоритм, дві машинирушій (валідація модуля)

Спільний мотив: ізоляція й перевірка відбуваються якомога раніше — в ядрі, компіляторі або при завантаженні модуля, а не «десь у рантаймі, якщо пощастить»

Три межі — одна картина

ПРОЦЕС межа середовища ПАМʼЯТЬ межа безпеки ІНСТРУКЦІЇ межа набору Docker · namespaces Rust · borrow checker asm → WebAssembly ТРИ МЕЖІ МІЖ ПРОГРАМОЮ І МАШИНОЮ

Підсумковий слайд усього курсу, не лише лекції

Не можна довіряти коду, який ви не створили повністю самі. Жоден обсяг перевірки чи аналізу вихідного тексту не захистить вас від використання недовіреного коду.

— Кен Томпсон, «Reflections on Trusting Trust» (Тюрінгівська лекція, 1984)

Підсумок

  • Rust компілюється в WASM тим самим rustc — інша ціль компіляції
  • wasm-bindgen генерує міст Rust↔JS і бере на себе маршалінг рядків через лінійну пам'ять
  • wasm-pack — конвеєр збірки: rustc + wasm-bindgen CLI + пакування в один виклик
  • Відтворювані збірки в Docker гарантують, що опублікований бінарник відповідає коду

🥚 У демо-терміналі хеш зібраного модуля починається з c1d0…: перші три шістнадцяткові цифри — 0xC1D, тобто 3101. Це число супроводжувало приклади від перших лекцій; тут воно замикає коло.

VTFK · TERMLINK · L10← → · space · f — на весь екран