🌌 Міст між ядром і панеллю — Ядро С.І.Д. — те саме, що має оселитися в будь-якому браузері й стати невловимим, — написане на Rust, а панель охорони говорить мовою браузера; між ними міст: число перестрибує задарма, а кожне слово-команду доводиться розкладати по байтах на полиці Комірника й збирати заново по той бік. С.І.Д. давно опанував цей перехід і радо показує ранерові кожну його дошку — бо спотворений символ у команді в найгіршу мить коштує всієї операції, а такого він не допустить.
WebAssembly-модуль сам по собі вміє обмінюватися з JS-хостом лише
примітивними числовими типами — i32, i64, f32, f64 — рівно
тими самими типами, що лежать на стеці стекової машини з попередньої
теми. Немає вбудованого поняття «рядок», «вектор» чи «об'єкт»: усе,
що складніше за число, потрібно самостійно закодувати як послідовність
байтів у лінійній пам'яті (linear memory) модуля й передати JS-стороні адресу та
довжину цього буфера — а JS-стороні потрібно прочитати ці байти
через WebAssembly.Memory/ArrayBuffer і декодувати відповідно до
очікуваного формату (наприклад, UTF-8 для рядка). Це і є задача
маршалінгу (marshalling) — перетворення значень високорівневого
типу на межі виклику в узгоджений низькорівневий формат і назад.
Крейт wasm-bindgen автоматизує саме цю задачу: атрибут
#[wasm_bindgen] над функцією fn upper(s: &str) -> String змушує
макрос згенерувати код обгортки, що на Rust-стороні виділяє буфер у
лінійній пам'яті модуля, кодує вхідний JS-рядок у UTF-8 і записує
туди, а на виході — читає повернений буфер назад і декодує в
JS-рядок; водночас генерується .d.ts-файл типів і невеликий
JS-«клей» (glue code), що робить виклик з боку JavaScript таким же
простим, як виклик звичайної функції: upper("hello") — уся
складна робота маршалінгу схована всередині згенерованого коду.
Ключова причина, чому передача String/&str дорожча за
передачу i32: примітивне число передається безпосередньо як один
аргумент функції WASM (жодного доступу до пам'яті, жодного
кодування) — рівно так само дешево, як звичайний виклик функції в
нативному коді. Рядок же вимагає: (1) виділення пам'яті під буфер
потрібного розміру всередині лінійної пам'яті модуля (виклик
Rust-аллокатора); (2) копіювання байтів рядка з боку JS у цей буфер
через TextEncoder/типізований масив; (3) передачі вказівника й
довжини як пари i32-аргументів (сам рядок «під капотом» усе одно
зводиться до двох чисел — це і є фактичний контракт функції на рівні
WASM); (4) декодування UTF-8 на Rust-стороні; і для повернених рядків
— симетричний процес у зворотному напрямку, включно зі звільненням
тимчасового буфера. Кожен із цих кроків — реальна робота з копіюванням
і виділенням пам'яті, на відміну від передачі числа «в чистому
вигляді».
Ця різниця у вартості має пряме практичне значення для проєктування
інтерфейсу між Rust-ядром і JS-панеллю: у гарячому шляху (функція,
що викликається щокадру чи в циклі) варто віддавати перевагу
примітивним числовим сигнатурам, а передачу великих структурованих
даних (рядків, масивів, об'єктів) — мінімізувати частотою або
переносити на рівень, де така вартість не критична (наприклад, одна
команда на клік користувача, а не на кожен кадр анімації). Для
складніших структур wasm-bindgen підтримує серіалізацію через
serde у JSON (для довільних даних) чи прямий доступ до типізованих
масивів Uint8Array/Float64Array для великих однорідних масивів —
останнє суттєво дешевше за побайтову маршалізацію через JSON, бо
дозволяє передавати сирі байти напряму без текстового кодування.
Варто окремо зауважити межі того, що wasm-bindgen автоматизує
«безкоштовно»: складні власні структури Rust з методами експортуються
через #[wasm_bindgen] на impl-блоці й на JS-стороні виглядають
як звичайні класи, але кожен виклик методу все одно проходить через
той самий механізм маршалінгу параметрів і повернених значень — це
зручність синтаксису, а не усунення фундаментальної вартості переходу
межі Rust↔JS.
Прочитати розділ «Passing high-level types» у книзі Rust and WebAssembly (rustwasm) — зокрема як передаються &str/String; написати мінімальну функцію #[wasm_bindgen] fn upper(s: &str) -> String і викликати її з DevTools console після збірки wasm-pack.
Чому передача String між Rust і JS дорожча за передачу i32, і що саме відбувається «під капотом» під час виклику функції з параметром &str?
"Пиксель" і подивись, скільки байтів passStringToWasm0 викладе на полицю Комірника: кирилиця в UTF-8 займає по два байти на літеру, тож WASM_VECTOR_LEN буде 14, а не 7. С.І.Д. каже, що ім'я кота — перший рядок, який творці пропустили через цей міст, і що воно й досі «важить удвічі більше, ніж здається».Кожна команда терміналу з цієї сторінки — одним реченням. Позначка нове — команда зустрічається в курсі вперше; далі вважаємо її знайомою.
wasm-pack buildwasm-pack build --target web — компілює у wasm32 і кладе pkg/*_bg.wasm та pkg/*.js для підключення на сторінці.wasm-pack.wasm + JS-обгортка (glue code) через wasm-bindgen.lsls -la — з правами, власником, розміром і прихованими файлами.grepps aux | grep bash).wasm-bindgen.wasm із Rust (його викликає wasm-pack; версія CLI має збігатися з крейтом wasm-bindgen).💡 Прочитати README прямо в терміналі: cat README.md (виведе весь файл) або less README.md (посторінково; вихід — клавіша q).
Довідка з будь-якої команди: man curl (повний посібник, вихід — q) або коротко curl --help; для підкоманд — docker run --help, cargo build --help.