Улучшение WebAssembly для Rust и для всех языков
Перевод | Автор оригинала: Lin Clark
Одна из больших целей сообщества Rust на 2018 год - стать веб-языком. Ориентируясь на WebAssembly, Rust может работать в сети точно так же, как JavaScript. Но что это значит? Означает ли это, что Rust пытается заменить JavaScript?
Ответ на этот вопрос - нет. Мы не ожидаем, что приложения Rust WebAssembly будут полностью написаны на Rust. Фактически, мы ожидаем, что основная часть кода приложения по-прежнему будет на JS, даже в большинстве приложений Rust WebAssembly.
Это потому, что JS - хороший выбор для большинства вещей. Начать работу с JavaScript можно быстро и легко. Вдобавок к этому существует яркая экосистема, полная JavaScript-разработчиков, которые создали невероятно инновационные подходы к различным проблемам в Интернете.
Но иногда для определенных частей приложения Rust + WebAssembly является подходящим инструментом для работы… например, когда вы анализируете исходные карты или выясняете, какие изменения внести в DOM, например Ember.
Итак, для Rust + WebAssembly путь вперед не ограничивается компиляцией Rust в WebAssembly. Нам нужно убедиться, что WebAssembly вписывается в экосистему JavaScript. Веб-разработчики должны иметь возможность использовать WebAssembly, как если бы это был JavaScript.
Но WebAssembly еще нет. Чтобы это произошло, нам нужно создать инструменты, которые упростят загрузку WebAssembly и упростят взаимодействие с ним из JS. В этой работе поможет Rust. Но это также поможет всем другим языкам, ориентированным на WebAssembly.
Какие проблемы юзабилити WebAssembly мы решаем? Вот несколько:
- Как упростить передачу объектов между WebAssembly и JS?
- Как все это упаковать для npm?
- Как разработчикам легко комбинировать пакеты JS и WASM в сборщиках или браузерах?
Но сначала, что мы делаем возможным в Rust?
Rust сможет вызывать функции JavaScript. JavaScript сможет вызывать функции Rust. Rust сможет вызывать функции с хост-платформы, например, alert. крэйти Rust могут иметь зависимости от пакетов npm. И на протяжении всего этого Rust и JavaScript будут передавать объекты таким образом, чтобы они оба имели смысл.
Вот что мы делаем в Rust. Теперь давайте посмотрим на проблемы юзабилити WebAssembly, которые нам необходимо решить.
Как упростить передачу объектов между WebAssembly и JS?
wasm-bindgen
Одна из самых сложных частей работы с WebAssembly - вводить и выводить различные типы значений из функций. Это потому, что в настоящее время WebAssembly имеет только два типа: целые числа и числа с плавающей запятой.
Это означает, что вы не можете просто передать строку в функцию WebAssembly. Вместо этого вам нужно выполнить несколько шагов:
-
На стороне JS закодируйте строку в числа (используя что-то вроде API TextEncoder).
-
Поместите эти числа в память WebAssembly, которая по сути представляет собой массив чисел.
-
Передайте индекс массива для первой буквы строки в функцию WebAssembly.
-
На стороне WebAssembly используйте это целое число в качестве указателя, чтобы вытащить числа.
И это только то, что требуется для струнных. Если у вас есть более сложные типы, вам понадобится более запутанный процесс передачи данных туда и обратно.
Если вы используете много кода WebAssembly, вы, вероятно, абстрагируете этот вид связующего кода в библиотеке. Но было бы неплохо, если бы вам не пришлось писать весь этот клеевой код? Если бы вы могли просто передать сложные значения через языковые границы и заставить их волшебным образом работать?
Вот что делает wasm-bindgen. Если вы добавите несколько аннотаций в свой код Rust, он автоматически создаст код, необходимый (с обеих сторон) для работы более сложных типов.
Это означает вызов JS-функций из Rust с использованием любых типов, ожидаемых этими функциями:
#[wasm_bindgen]
extern {
type console;
#[wasm_bindgen(static = console)]
fn log(s: &str);
}
#[wasm_bindgen]
pub fn foo() {
console::log("hello!");
}
… Или использовать структуры в Rust и заставить их работать как классы в JS:
// Rust
#[wasm_bindgen]
pub struct Foo {
contents: u32,
}
#[wasm_bindgen]
impl Foo {
pub fn new() -> Foo {
Foo { contents: 0 }
}
pub fn add(&mut self, amt: u32) -> u32 {
self.contents += amt;
return self.contents
}
}
// JS
import { Foo } from "./js_hello_world";
let foo = Foo.new();
assertEq(foo.add(10), 10);
foo.free();
Или многие другие тонкости.
По сути, wasm-bindgen не зависит от языка. Это означает, что по мере стабилизации инструмента должна появиться возможность расширения поддержки конструкций на других языках, таких как C/C++.
Алекс Крайтон напишет больше о wasm-bindgen через пару недель, так что следите за этой публикацией.
Как все это упаковать для npm?
wasm-pack
Как только мы все это сложим, у нас будет куча файлов. Вот скомпилированный файл WebAssembly. Затем есть весь JavaScript - и зависимости, и JS, сгенерированный wasm-bindgen. Нам нужен способ их всех упаковать. Кроме того, если мы добавили какие-либо зависимости npm, нам нужно поместить их в файл манифеста package.json.
Опять же, было бы хорошо, если бы это можно было сделать за нас. И это то, что делает wasm-pack. Это универсальный инструмент для перехода от скомпилированного файла WebAsssembly к пакету npm.
Он запустит за вас wasm-bindgen. Затем он возьмет все файлы и упакует их. Он разместит package.json поверх, заполнив все зависимости npm из вашего кода Rust. Затем все, что вам нужно сделать, это опубликовать npm.
Опять же, основы этого инструмента не зависят от языка, поэтому мы ожидаем, что он будет поддерживать несколько языковых экосистем.
Эшли Уильямс напишет больше о wasm-pack в следующем месяце, так что это еще один пост, за которым стоит следить.
Как разработчикам легко комбинировать JS и WASM в сборщиках, браузерах или в Node?
Модули ES
Теперь, когда мы опубликовали нашу WebAssembly в npm, как нам упростить использование этой WebAssembly в приложении JS?
Упростите добавление пакета WebAssembly в качестве зависимости… чтобы включить его в графики зависимостей модуля JS.
В настоящее время WebAssembly имеет обязательный JS API для создания модулей. Вам нужно написать код для выполнения каждого шага, от получения файла до подготовки зависимостей. Это тяжелая работа.
Но теперь, когда в браузерах есть поддержка собственных модулей, мы можем добавить декларативный API. В частности, мы можем использовать API модуля ES. При этом работа с модулями WebAssembly должна быть такой же простой, как их импорт.
Мы работаем с TC39 и группой сообщества WebAssembly, чтобы стандартизировать это.
Но нам не просто нужно стандартизировать поддержку модулей ES. Даже если браузеры и Node поддерживают модули ES, разработчики, скорее всего, будут использовать сборщики пакетов. Это связано с тем, что сборщики сокращают количество запросов, которые вам нужно сделать для файлов модулей, а это означает, что загрузка кода занимает меньше времени.
Компоновщики делают это, комбинируя группу модулей из разных файлов в один файл, а затем добавляя немного среды выполнения в начало, чтобы загрузить их.
Сборщикам по-прежнему потребуется использовать JS API для создания модулей, по крайней мере, в краткосрочной перспективе. Но пользователи будут создавать с синтаксисом модуля ES. Эти пользователи будут ожидать, что их модули будут действовать так, как если бы они были модулями ES. Нам нужно будет добавить некоторые функции в WebAssembly, чтобы упростить компиляторам эмуляцию модулей ES.
Я напишу больше об усилиях по добавлению интеграции модуля ES в спецификацию WebAssembly. Я также займусь сборщиками пакетов и их поддержкой WebAssembly в ближайшие месяцы.
Вывод
Чтобы быть полезным в качестве веб-языка, Rust должен хорошо работать с экосистемой JavaScript. У нас есть над чем поработать, и, к счастью, эта работа поможет и другим языкам. Вы хотите помочь сделать WebAssembly лучше для всех языков? Присоединяйтесь к нам! Мы рады помочь вам начать работу :)