Экспорт и импорт

Учимся открывать функции наружу и принимать функции снаружи.

Export делает внутреннюю сущность модуля видимой для хоста, а import — наоборот, объявляет, что модуль ждёт извне.

Зачем экспорт

Сама по себе функция внутри модуля недоступна снаружи — её имя $add существует только внутри. Чтобы JavaScript смог её вызвать, функцию нужно экспортировать под публичным строковым именем:

(module
  (func $add (param $a i32) (param $b i32) (result i32)
    local.get $a
    local.get $b
    i32.add)
  (export "add" (func $add)))

Теперь из JS функция доступна как instance.exports.add. Обратите внимание: внутреннее имя $add и публичное "add" — разные вещи, их можно называть по-разному. Экспортировать можно не только функции, но и память, таблицы и глобальные переменные.

Сокращённая форма экспорта

Экспорт можно объявить прямо при определении функции — это короче:

(module
  (func (export "add") (param $a i32) (param $b i32) (result i32)
    local.get $a
    local.get $b
    i32.add))

Зачем импорт

Wasm-модуль изолирован: у него нет доступа к консоли, времени, случайным числам — ни к чему из внешнего мира. Чтобы что-то получить снаружи, модуль импортирует функцию, обещая её сигнатуру. Хост (JS) обязан предоставить реализацию при создании экземпляра. Импортируем функцию логирования:

(module
  (import "console" "log" (func $log (param i32)))
  (func (export "run")
    i32.const 42
    call $log))      ;; вызовет переданную снаружи функцию

Здесь "console" — это модуль импорта, "log" — имя внутри него. JS при инстанцировании передаст объект { console: { log: (x) => ... } }.

Граница изоляции

Импорты и экспорты — это единственные двери в модуль и из него. Wasm не может вызвать что-либо, что ему явно не импортировали, и не отдаёт ничего, что явно не экспортировал. Это основа модели безопасности Wasm: песочница с чётко описанным интерфейсом.

         импорты (что нужно снаружи)
              |
              v
        +-------------+
  -->   |   МОДУЛЬ     |   -->  экспорты (что отдаёт наружу)
        +-------------+
   (всё остальное — недоступно)

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

При создании экземпляра (WebAssembly.instantiate) движок сверяет импорты модуля с переданным объектом import. Если модуль ждёт console.log с сигнатурой (param i32), а вы не передали такую функцию — инстанцирование упадёт с ошибкой ещё до запуска кода. Это проверка контракта: модуль не запустится, пока все его внешние зависимости не удовлетворены.

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

  • Забыли экспорт — функция есть, но instance.exports.add равно undefined.
  • Не передали импорт — модуль ждёт env.log, а в import-объекте его нет → ошибка инстанцирования.
  • Несовпадение сигнатур импорта — модуль ждёт (param i32), а вы рассчитываете на два аргумента: контракт нарушен.

Итоги

  • export открывает функцию/память наружу под строковым именем.
  • import объявляет внешнюю зависимость, которую хост обязан предоставить.
  • Импорты и экспорты — единственные двери модуля, основа песочницы.
  • Несовпадение контракта импорта ломает инстанцирование сразу.
Проверьте себя
1. Что произойдёт, если функцию не экспортировать?
AМодуль не скомпилируется
BСнаружи к ней нельзя обратиться: instance.exports не содержит её
CОна будет вызвана автоматически
DWasm экспортирует все функции по умолчанию
2. Зачем модулю Wasm нужны импорты?
AЧтобы ускорить вычисления
BЧтобы получить доступ к внешнему миру (лог, время и т.п.), которого у него нет
CЧтобы уменьшить размер файла
DИмпорты в Wasm не нужны
3. Что случится при инстанцировании, если не передать ожидаемый импорт?
AМодуль запустится с заглушкой
BИнстанцирование завершится ошибкой ещё до выполнения кода
CИмпорт станет null и код продолжит работу
DНичего, импорты опциональны