Go и AssemblyScript в Wasm

Смотрим на два других популярных пути компиляции в Wasm.

AssemblyScript — это язык, похожий на TypeScript, который компилируется прямо в Wasm; Go компилируется со своим рантаймом и GC.

Go: привычный язык, но с весом

Go умеет компилировать в Wasm командой с особыми переменными окружения. Это удобно: вы пишете обычный Go и получаете .wasm. Но есть нюанс — у Go есть сборщик мусора и рантайм планировщика горутин, и всё это компилируется в модуль. Поэтому модули Go тяжелее, чем у Rust или C.

GOOS=js GOARCH=wasm go build -o main.wasm main.go
# Go тащит свой рантайм и GC в .wasm — модуль крупнее

Существует также TinyGo — альтернативный компилятор, который даёт куда меньшие Wasm-модули за счёт урезанного рантайма, ценой поддержки не всех возможностей языка. Для embedded и Wasm TinyGo часто предпочтительнее.

AssemblyScript: TypeScript-подобный язык

AssemblyScript — отдельный язык, синтаксически почти неотличимый от TypeScript, но компилирующийся напрямую в Wasm. Он привлекателен для веб-разработчиков: знакомый синтаксис, но скорость Wasm. Платой за это является строгая статическая типизация и явные числовые типы. Исходник AssemblyScript помечаем language-text:

// AssemblyScript: похоже на TS, но типы i32 явные
export function add(a: i32, b: i32): i32 {
  return a + b;
}

export function fib(n: i32): i32 {
  if (n < 2) return n;
  return fib(n - 1) + fib(n - 2);
}

Заметьте типы i32 вместо абстрактного number — AssemblyScript оперирует теми же четырьмя типами Wasm напрямую, поэтому компиляция предсказуема и быстра.

Проверим логику fib на JS

Алгоритм одинаков на любом языке. Повторим тот же Фибоначчи на чистом JS и убедимся в значениях:

function fib(n) {
  if (n < 2) return n;
  return fib(n - 1) + fib(n - 2);
}
console.log("fib(10) =", fib(10));
console.log("fib(15) =", fib(15));

Вывод:

fib(10) = 55
fib(15) = 610

Сравнение путей

ЯзыкGCРазмер модуляКогда выбрать
Rustнетмалыймаксимум скорости/компактности
C/C++ (Emscripten)нетсреднийпортирование готовых библиотек
Goдакрупныйзнаком, но вес терпим
AssemblyScriptда (свой)малый-среднийвеб-разработчику, знакомый синтаксис

Как работает под капотом

Разница в размере модулей объясняется рантаймом. Rust и C не тащат сборщик мусора — в модуле почти только ваш код. Go включает свой GC и планировщик, поэтому даже пустая программа весит сотни килобайт. AssemblyScript имеет минималистичный собственный рантайм с GC, что держит модули небольшими. Выбор языка — это компромисс между знакомым синтаксисом и весом/скоростью результата.

Частые ошибки

  • Удивляться размеру Go-модуля — это рантайм и GC; для лёгких задач берите TinyGo.
  • Считать AssemblyScript «просто TypeScript» — это отдельный язык с явными типами и без многих возможностей TS.
  • Игнорировать типы в AssemblyScript — абстрактный number там не подойдёт, нужны i32/f64.

Итоги

  • Go компилируется в Wasm, но тащит GC и рантайм — модули крупные (есть TinyGo).
  • AssemblyScript — TS-подобный язык, компилируется прямо в Wasm, типы явные.
  • Выбор языка — компромисс знакомства, веса и скорости.
Проверьте себя
1. Почему Wasm-модули, скомпилированные из Go, обычно крупнее?
AGo использует длинные имена
BGo тащит свой сборщик мусора и рантайм планировщика в модуль
CGo не оптимизирует код
DWasm не сжимает Go
2. Что такое AssemblyScript?
AДиалект ассемблера
BОтдельный TypeScript-подобный язык, компилируемый прямо в Wasm с явными типами
CИнструмент сжатия Wasm
DРантайм для Go
3. Что обеспечивает компактность модулей Rust для Wasm?
AШифрование кода
BОтсутствие сборщика мусора — в модуле почти только ваш код
CИспользование интерпретатора
DГотовый рантайм в браузере