Мы уже видели насколько WebAssembly быстро компилируется, ускоряет js библиотеки и генерирует более компактные бинарники. У нас даже есть общее представление как наладить взаимодействие не только между сообществами Rust и JavaScript, но и с сообществами других языков. В прошлой статье мы упоминали специальный инструмент wasm-bindgen и сейчас я бы хотел остановиться на нем более подробно. На данный момент спецификация WebAssembly описывает только четыре типа данных: два целочисленных и два с плавающей точкой. Однако большую часть времени JS и Rust разработчики используют куда более богатую систему типов. Например, JS разработчики взаимодействуют с объектом document для того чтоб добавить или изменить узлы HTML, в то время как Rust разработчики работают с такими типами как Result для обработки ошибок, и практически все разработчики работают со строками. Быть ограниченными только теми типами, которые определяет WebAssembly, было бы слишком неудобно и тут нам на помощь приходит wasm-bindgen. Основная задача wasm-bindgen — это предоставить мост между системами типов Rust и JS. Он позволяет JS функции вызывать Rust API передавая обычные строки или Rust функции перехватить исключение из JS. wasm-bindgen компенсирует несовпадения типов и дает возможность эффективного и простого использования WebAssembly функций из JavaScript и обратно.  Более подробное описание проекта wasm-bindgen вы можете найти на нашем README. Для начала давайте разберем простой пример использования wasm-bindgen, а потом посмотрим как вы еще сможете его использовать.  Вечная классика. Один из лучших способов попробовать новый инструмент — это изучить его вариацию вывода сообщения "Привет мир". В данном случае мы рассмотрим пример, который делает именно это — выводит диалоговое окно с надписью "Hello World".  Цель здесь проста, мы хотим создать Rust функцию, которая получая имя выводит диалоговое окно с надписью Hello, ${name}!. В JavaScript мы бы описали ее так: Однако мы хотим написать эту функцию на Rust. Для того чтоб это работало, нам потребуются следующие шаги Для начала создадим новый Rust проект: Эта команда создаст папку wasm-greet, в которой мы с вами будем работать. Следующим шагом надо добавить в наш Cargo.toml (аналог package.jsonдля Rust) следующую информацию: Содержимое секции lib мы пока пропустим, а в секции dependencies мы указываем зависимость нашего проекта от пакета wasm-bindgen. Этот пакет включает в себя все необходимое для использования wasm-bindgen в нашем проекте. А теперь давайте добавим немного кода! Замените содержимое src/lib.rs следующим кодом: Если вы не знакомы с Rust, пример выше может показаться вам немного многословным, но не волнуйтесь. Проект wasm-bindgen постоянно совершенствуется и я уверен, что в будущем необходимость столь подробного описания будет устранена. Наиболее важная часть здесь это аттрибут #[wasm_bindgen]. Это аннотация в Rust, которая говорит, что эту функцию надо при необходимости обернуть в другую функцию. Обе наши функции(и импорт функции alertи экспорт функции greet) имеют данный аттрибут. Чуть позже мы заглянем "под капот" и посмотрим что там происходит. Но сначала давайте скомпилируем наш wasm код и откроем его в браузере: По завершению мы получим wasm файл, который будет находиться target/wasm32-unknown-unknown/debug/wasm_greet.wasm. Если мы воспользуемся чем-то вроде wasm2wat и заглянем внутрь этого файла, его содержимое может показаться немного пугающим. Оказывается, что wasm файл еще не готов для использования из JS. Для этого нам потребуется еще один шаг: Как раз на этом шаге и происходит вся магия. Команда wasm-bindgen выполняет обработку wasm файла и делает его готовым к использованию. Чуть позже мы рассмотрим что значит "готов к использованию", а сейчас достаточно сказать, что если мы импортируем только что созданный модуль wasm_greet.js, то там будет содержаться функция greet, которая объявлена в Rust.  Теперь мы можем использовать упаковщик и создать HTML страницу, на которой и выполнится наш код. На момент написания этой статьи только Webpack 4.0 имеет достаточную поддержку WebAssembly чтоб работать из коробки(однако на данный момент есть проблема с браузером Хром). Несомненно, со временем все больше упаковщиков будут добавлять поддержку WebAssembly. Я не буду вдаваться в детали. Вы можете посмотреть примерную конфигурацию для WebPack в репозитории. Если мы посмотрим на содержимое нашего JS файла, то увидим следующее: … и на этом все. Открыв нашу страницу в браузере мы увидим диалоговое окно с надписью Hello, World!, которое создано в Rust. Фух, это был довольно большой Hello, World!. Давайте посмотрим немного на то, что происходит под капотом и как этот инструмент работает.  Один из наиболее важных аспектов wasm-bindgen — это то, что интеграция основана на фундаментальной концепции что wasm модуль это просто другой тип ES модуля. В примере выше мы просто хотели создать ES модуль со следующей сигнатурой(TypeScript): У WebAssembly нет возможности сделать это(помните, что на данный момент wasm поддерживает только числа), по этому мы используем wasm-bindgen чтоб заполнить пробелы. На последнем шаге прошлого примера, когда мы запустили команду wasm-bindgen она создала не только файл wasm_greet.js, но и wasm_greet_bg.wasm. Первый — это и есть наш JS интерфейс, который и позволяет нам вызвать Rust код. А файл *_bg.wasm содержит реализацию и весь скомпилированный код.  Когда мы импортируем модуль ./wasm_greet, мы получаем тот Rust код, который бы хотели вызывать из JS, но на данном этапе у нас нет возможности делать это нативно. Теперь, когда мы рассмотрели процесс интеграции, давайте посмотрим на выполнение этого кода.  Здесь мы асинхронно импортируем наш интерфейс, ждем пока он будет готов(скачивание и компиляция wasm модуля), и вызываем функцию greet.  Если мы посмотрим на содержимое файла wasm_greet.js, который был сгенерирован wasm-bindgen, то мы увидим нечто подобное: Здесь мы видим как wasm-bindgen сгенерировал для нас функцию greet. Под капотом он все еще вызывает функцию greet и wasm модуля, но теперь она вызывается не со строкой, а с передачей указателя и длины в качестве аргументов. Больше информации о функции passStringToWasm вы можете найти в статье от Lin Clark. Если бы мы не использовали wasm-bindgen, нам бы пришлось написать весь этот код самостоятельно. Чуть позже мы вернемся к функции __wbg_f_alert_alert_n.  Спустившись на уровень ниже, мы найдем следующий интересный пункт — функция greet в WebAssembly. Давайте посмотрим на код, который видит компилятор Rust. Обратите внимание, что подобно JS коду, который сгенерирован выше, вы не писали руками экспортируемый символ greet. wasm-bindgen сгенерировал все необходимое самостоятельно, а именно: Здесь мы видим нашу функцию greet, а так же дополнительно сгенерированную при помощи аттрибута #[wasm_bingen] функцию __wasm_bindgen_generated_greet. Это и есть экспортируемая функция (на это указывает аттрибут #[export_name] и ключевое слово extern), которая принимает указатель и длину строки. Затем он конвертирует эту пару в &str(строка в Rust) и передает её нашей функции greet. Другими словами wasm-bindgen генерирует две обёртки: одну в JavaScript, которая преобразует типы из JS в wasm и одну в Rust, которая принимает типы wasm и конвертирует в Rust. Хорошо, давайте посмотрим на последний набор оберток для функции alert. Функция greet в Rust использует стандартный макрос format! для создания новой строки и затем передает её функции alert. Помните, когда мы объявили функцию alert, мы использовали аттрибут #[wasm_bindgen], теперь давайте посмотрим, что увидит компилятор Rust: Это не совсем то, что мы написали, но зато мы можем здесь наглядно видеть что происходит. Функция alert на самом деле это тонкая обертка, которая принимает строку &str и далее конвертирует его в понятные для wasm числа. Затем вызывается функция __wbg_f_alert_alert_n и тут есть любопытная часть — это аттрибут #[wasm_import_module].
Для того чтоб импортировать функцию в WebAssembly нужен модуль, который её содержит. И так как wasm-bindgen построен на ES модулях то импорт такой функции из wasm будет интерпретирован как import из ES модуля. Модуль __wbindgen_placeholder__ на самом деле не существует, эта срока указывает на то, что это импорт должен быть обработан wasm-bindgen и сгенерирована обертка для JS. И, наконец, мы получаем наш последний кусочек пазла — сгенерированный JS файл, который содержит: Как выяснилось, довольно много всего происходит под капотом и мы прошли довольно долгий путь для вызова JS функции в браузере. Но не переживайте, ключевой аспект wasm-bindgen в том, что все это скрыто. Вы можете просто писать Rust код с несколькими аттрибутами #[wasm_bindgen] тут и там. А потом ваш JS код сможет его использовать так, как будто это еще один JavaScript модуль.  Проект wasm-bindgen весьма амбициозен, охватывает большую область и на данный момент у меня нет достаточного количества времени чтоб все описать. Хороший способ увидеть его в деле — это ознакомиться с нашими примерами, от простого Hello World!, до манипуляции узлами DOM дерева из Rust. В общих чертах, основные возможности wasm-bindgen: Если вам интересно узнать о дополнительных функциях следите за нашим трекером. До завершения я бы хотел рассказать немного о будущем проекта wasm-bindgen так как это одна из самых волнительных тем.  С самого первого дня wasm-bindgen был спроектирован с прицелом на то, что он сможет быть использован из многих языков. В то время как Rust пока что единственный поддерживаемый язык, инструмент позволит в дальнейшем так же добавить C/C++. Аттрибут #[wasm_bindgen] создает дополнительную секцию в файле .wasm, которую парсит и затем удаляет wasm-bindgen. В этой секции описано какие биндинги надо сгенерировать в JS и их интерфейс. В этой секции нет ничего Rust-специфичного, так что плагин с С/С++ компилятору так же сможет создать ее, чтоб потом была возможность использовать wasm-bindgen. Для меня это наиболее волнующий момент потому что я верю, что именно это позволит инструментам вроде wasm-bindgen стать стандартом для обеспечения взаимодействия WebAssembly и JS. Я надеюсь, что возможность обойтись без лишнего конфигурационного кода станет преимуществом для всех языков, которые могут быть скомпилированы в WebAssembly. На данный момент, один из недостатков при импортировании JS функции с помощью #[wasm_bindgen] — это то, что вам надо описывать все функции самостоятельно и следить за тем, чтоб не возникло ошибок. Временами этот процесс может быть весьма утомительным(и быть источником ошибок) и он требует автоматизации.  Все Web API указаны и описаны в WebIDL и это должно быть вполне возможно сгенерировать все биндинги автоматически из WebIDL. Это означает, что вам не надо будет определять функцию alert как мы делали в примере выше, вместо этого вы могли бы написать что-то вроде этого: В этом случае пакет webapi мог бы быть автоматически сгенерирован из описаний WebIDL API и это бы гарантировало отсутствие ошибок.  Мы можем развить эту идею еще дальше и использовать впечатляющую работу TypeScript сообщества и генерировать биндинги так же из TypeScript. Это позволит автоматически использовать любой пакет с npm у которого есть поддержка TypeScript.  И последний по порядку, но не последний по значимости на горизонте wasm-bindgen, супер быстрые манипуляции c DOM — святой грааль многих JavaScript фреймворков. Сегодня все вызовы функций для работы с DOM проходят через дорогостоящие преобразования при переходе от JavaScript к C++ движкам. С помощью WebAssembly эти преобразования могут стать необязательными. Известно, что система типов WebAssembly… есть! Генерация кода wasm-bindgen с самого первого дня спроектирована с прицелом на поддержку бидингов к хосту. Как только эта функция появится в WebAssembly, у нас будет возможность напрямую использовать импортированные функции без оберток, которые генерирует wasm-bindgen. Более того, это позволит JS движкам агрессивно оптимизировать манипуляции с DOM из WebAssembly, так как все интерфейсы будут строго типизированны и больше не будет необходимости их валидировать. И в таком случае wasm-bindgen не только сделает проще работу с различными типами данных, но и обеспечит лучшую в своем роде производительность при работе с DOM.  Я считаю работу с WebAssembly невероятно интересной не только из-за сообщества, но так же из-за того с какой скоростью он развивается. У проекта wasm-bindgen светлое будущее. Он не только обеспечивает простую интероперабельность между JS и Rust, но и в долгосрочной перспективе откроет новые возможности по мере развития WebAssembly.  Попробуйте wasm-bindgen, создайте запрос на новую функцию, и оставайтесь на связи с Rust и WebAssembly. Программист