Rust в Wasm: wasm-pack и wasm-bindgen
Разбираем, почему Rust считается лучшим языком для Wasm.
wasm-bindgen — это инструмент, который автоматически генерирует «клей» между Rust и JS, скрывая ручную работу с памятью.
Почему Rust и Wasm — пара
Rust подходит к Wasm почти идеально по трём причинам. Во-первых, у Rust нет сборщика мусора — он управляет памятью через систему владения на этапе компиляции, что точно ложится на безGC-модель Wasm и даёт компактные модули. Во-вторых, Rust безопасен по памяти без накладных расходов — нет утечек и обращений за границу. В-третьих, у Rust первоклассная поддержка Wasm как цели компиляции прямо «из коробки».
Функция на Rust
Вот функция на Rust, готовая к экспорту в Wasm. Исходник Rust помечаем language-text — он компилируется тулчейном, а не запускается в браузере:
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
format!("Привет, {}!", name)
}
Атрибут #[wasm_bindgen] помечает функции для экспорта. Обратите внимание на greet: она принимает и возвращает строку — и нам не пришлось вручную возиться с адресами и длинами. Это и есть магия wasm-bindgen.
Сборка одной командой
Инструмент wasm-pack компилирует Rust в Wasm и упаковывает с JS-обёрткой и типами:
wasm-pack build --target web
# создаст pkg/ с .wasm, .js-обёрткой и .d.ts типами
Использование из JS
После сборки строковая функция вызывается из JS естественно, будто это обычная JS-функция:
import init, { add, greet } from "./pkg/my_module.js";
await init();
console.log(add(2, 3)); // 5
console.log(greet("Аня")); // "Привет, Аня!"
Что делает wasm-bindgen за вас
Помните ручную возню из урока про строки — кодировать в UTF-8, выделять память, передавать адрес и длину, потом освобождать? wasm-bindgen генерирует весь этот код автоматически. Проверим эквивалент того, что происходит внутри greet, на JS:
function greet(name) { // как Rust-функция после клея bindgen
return "Привет, " + name + "!";
}
console.log(greet("Аня"));
console.log(greet("мир"));
Вывод:
Привет, Аня! Привет, мир!
Как работает под капотом
На уровне Wasm функция greet всё равно принимает указатель и длину, а возвращает указатель на результат. wasm-bindgen генерирует две части: Rust-код, который раскодирует входные байты в &str и закодирует результат, и JS-код, который кодирует строку в память перед вызовом и декодирует результат после. Эти две стороны согласованы по формату. Вы пишете обычный Rust и обычный JS, а весь «протокол памяти» между ними генерируется.
Частые ошибки
- Забыть
#[wasm_bindgen]— функция не попадёт в экспорт и не будет видна из JS. - Не дождаться
init()— обёртка асинхронно грузит.wasm; вызов до инициализации упадёт. - Передавать тяжёлые структуры на каждый кадр — даже с bindgen копирование строк не бесплатно; для горячих циклов держите данные в памяти Wasm.
Итоги
- Rust идеален для Wasm: нет GC, безопасность памяти, поддержка из коробки.
#[wasm_bindgen]экспортирует функции и включает обмен сложными типами.wasm-pack buildсобирает.wasm+ JS-обёртку + типы.- wasm-bindgen автоматизирует протокол памяти для строк и структур.