Компиляция Rust в WebAssembly

Перевод не завершен. Пожалуйста, помогите перевести эту статью с английского.

Если уже вы написали некоторый код на Rust, вы можете скомпилировать его в WebAssembly! Из этого руководства вы узнаете всё, что вам нужно знать, чтобы скомпилировать проект на Rust в wasm и использовать его в существующем веб-приложении.

Примеры использования Rust и WebAssembly

Существует два основных варианта использования Rust и WebAssembly:

  • Чтобы создать целое приложение — целое веб-приложение, основанное на Rust!
  • Чтобы построить часть приложения — используйте Rust в существующем интерфейсе JavaScript.

На данный момент команда Rust фокусируется на последнем примере,  его мы рассмотрим здесь. Для первого примера, посмотрите проекты, такие как yew.

В этом руководстве вы создадите npm-пакет, используя wasm-pack, инструмент построения npm-пакетов в Rust. Этот пакет будет содержать только код WebAssembly и JavaScript, так что его пользователям не нужен будет установщик Rust. Они могут даже не заметить, что он был написан на WebAssembly!

Настройка окружения Rust

Давайте пройдемся по всем пунктам, необходимым для настройки нашего окружения.

Установка Rust

Чтобы установить Rust, посетите Install Rust страницу и проследуйте всем инструкциям. Так вы установите тулзу, называемую "rustup", которая позволит вам управлять несколькими версиями Rust. По умолчанию, она устанавливает последний стабильный релиз Rust, который вы будете использовать для стандартной разработки на Rust. Rustup устанавливает rustc, компилятор Rust, вместе с cargo, Rust-овским пакетным менеджером, rust-std, стандартной  библиотекой Rust, и несколькими вспомогательными доками — rust-docs.

Заметка: Обратите внимание на пост-установочную заметку о необходимости добавить cargo bin директорию в список PATH. Она должна быть добавлена автоматически, но вам нужно будет перезапустить терминал, чтобы изменения втсупили в силу.

wasm-pack

Чтобы собрать наш пакет, вам понадобится дополнительный инструмент, wasm-pack. Он поможет нам скомпилировать наш код в WebAssembly и создаст правильный контейнер для нашего пакета для npm. Чтобы скачать и установить, введите в терминале следующую команду:

$ cargo install wasm-pack

Установика Node.js и получение npm-аккаунта

В этом руководстве мы будем собирать npm-пакет, поэтому вам понадобится установить Node.js и npm. Дополнительно, мы опубликуем наш пакет на npm, так что вам так же понадобится ваш npm-аккаунт. Они бесплатны! Технически, вы не обязаны ничего публиковать, но так будет проще, так что будем считать, что вы следаете это в этом руковостве.

Чтобы получить Node.js и npm, посетите Get npm! страницу и проследуйте инструкциям. Когда настанет время выбрать версию, выберите любую, которая вам нравится; это руководство не зависит от версии.

Чтобы создать npm-аккаунт, посетите npm signup станицу и заполните форму.

Дальше запустите в командой строке npm adduser:

> npm adduser
Username: yournpmusername
Password:
Email: (this IS public) you@example.com

Вам понадобится ввести свое пользовательское имя, пароль и email. Если все получится, вы увидите:

Logged in as yournpmusername on https://registry.npmjs.org/.

Если что-то пойдет не так, свяжитесь с командой npm, чтобы разобраться.

Создание WebAssembly npm-пакета

Хватит установок, давайте создадим новый пакет на Rust. Перейдите в любое место, где вы держите свои личные проекты, и сделайте следующее:

$ cargo new --lib hello-wasm
     Создаст проектную библиотеку `hello-wasm`

Это создаст новую библиотеку в под-директории, называемой hello-wasm, со всем, что вам нужно:

+-- Cargo.toml
+-- src
    +-- lib.rs

Для начала, у нас есть Cargo.toml; с его помощью мы можем сконфигурировать наш билд. Если вы пользуетесь Gemfile из Bundler или package.json из npm, то вы почувствуете себя, как дома; Cargo работает аналогично обоим.

Дальше, Cargo сгенерировал кое-какой код для нас на Rust в src/lib.rs:

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}

Мы не будем использовать этот тестовый код вообще, так что можете просто удалить его.

Давайте попишем немного на Rust!

Вместо этого поместите этот код в src/lib.rs:

extern crate wasm_bindgen;

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
extern {
    pub fn alert(s: &str);
}

#[wasm_bindgen]
pub fn greet(name: &str) {
    alert(&format!("Hello, {}!", name));
}

Это содержимое нашего проекта на Rust. У него есть три основные части, давайте пройдемся по ним по очереди. Мы дадим здесь обощенное пояснение и поясним некоторые детали; чтобы узнать больше о Rust, пожалуйста, просмотрите бесплатную online-книгу The Rust Programming Language.

Использование wasm-bindgen для коммуникации между Rust и JavaScript

Первая часть выглядит вот так:

extern crate wasm_bindgen;

use wasm_bindgen::prelude::*;

Первая строка гласит: "эй, Rust, мы используем библиотеку, называемую wasm_bindgen." Библиотеки в Rust называются "crates" (контейнеры), а так как мы используем внешнюю, то "extern".

Поняли? Cargo поставляет контейнеры.

Третья строка содержит команду use, которая импортирует код из библиотеки в наш код. В нашем случае, мы импортируем все из модуля wasm_bindgen::prelude. Мы будем использовать его функции в следующей секции.

Прежде чем перейти к следующей секции, давайте поговорим немного о wasm-bindgen.

wasm-pack использует wasm-bindgen, другую тулзу, чтобы предоставить соединение между типами в JavaScript и Rust. Это позволяет JavaScript вызывать Rust-API со строками или функциям Rust перехватывать исключения JavaScript.

Мы будем использовать функциональность wasm-bindgen в нашем пакете. По факту, это следующая секция!

Вызов внешних функций JavaScript из Rust

Следующая часть выглядит так:

#[wasm_bindgen]
extern {
    pub fn alert(s: &str);
}

Частичка внутри #[] называется "атрибутом", и она кое-как модифицирует следующее за ней утверждение. В нашем случае, это утверждение extern, которое говорит Rust-у, что мы хотим вызвать некоторую функцию, определенную во внешнем пространстве. Атрибут говорит: "wasm-bindgen знает, как найти эти функции".

Третья строка это имя функции, написанной на Rust. Она говорит: "функция alert  принимает один аргумент, строку с именем s."

У вас, возможно, есть предположение, что это за функция, и, возможно, ваше предположение верное: это функция alert, предоставляемая JavaScript! Мы будем вызывать эту функцию в следующей секции.

Когда бы вы не захотели вызвать новую функцию JavaScript, вы можете написать ее здесь, и wasm-bindgen позаботится о том, чтобы настроить все для вас. Пока еще поддерживается не все, но мы работаем над этим! Пожалуйста, сообщайте о проблемах, если что-то было упущено.

Создание функций Rust, который может вызывать JavaScript

Финальная часть следующая:

#[wasm_bindgen]
pub fn greet(name: &str) {
    alert(&format!("Hello, {}!", name));
}

Еще раз, мы видим #[wasm_bindgen] атрибут. В этом случае, он модифицирует не блок extern, а fn; это значит, что мы хотим, чтобы эта функция на Rust была доступна для JavaScript. Прямо противоположно extern: это не те функции, которые нам нужны, а те, что мы предоставляем миру!

Наша функция называется greet, и она принимает один аргумент, строку (пишется &str), name. Затем она вызывает функцию alert, которую мы запросили в блоке extern выше. Она передает вызов макросу format!, который позволяет нам соеденить строки.

format! принимает два аргумента в нашем случае: форматируемую строку и переменную, которую должен в нее поместить. Форматируемая строка это "Hello, {}!" часть. Она содержит {}, куда будет вставлена переменная. Переменная, которую мы передаем, это name, аргумент функции, так что если мы вызовем greet("Steve"), то увидим "Hello, Steve!".

Все это передается в alert(), так что когда мы вызовем функцию, мы увидим алерт с "Hello, Steve!" внутри него!

Теперь, когда наша библиотека написана, давайте соберем ее.

Компиляция кода в WebAssembly

Чтобы правильно скомпилить наш код, сначала нам надо сконфигурировать его с помощью Cargo.toml. Октройте этот файл и измените его так, чтобы он выглядел следующим образом:

[package]
name = "hello-wasm"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]
description = "A sample project with wasm-pack"
license = "MIT/Apache-2.0"
repository = "https://github.com/yourgithubusername/hello-wasm"

[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "0.2"

Вам нужно будет ввести свой личный репозиторий, а Cargo заполнит authors, основываясь на информации git.

Главная часть находится внизу. Первая — [lib] — говорит Rust собрать cdylib версию нашего пакета; мы не будем вдаваться в то, что это значит в этом руководстве. Чтобы узнать больше, просмотрите Cargo и Rust Linkage документацию.

Вторая часть это секция [dependencies] . Тут мы говорим Cargo, от какой версии wasm-bindgen мы хотим зависеть; в нашем случае, это любая версия 0.2.z (но не 0.3.0 или выше).

Сборка пакета

Теперь, когда мы все установили, давайте соберем проект! Введите это в терминале:

$ wasm-pack build --scope mynpmusername

Здесь мы сделали несколько вещей (и они займут много времени, особенно если вы запустили wasm-pack впервые). Чтобы изучить их детальней, прочитайте этот блог-пост на Mozilla Hacks. Вкратце, wasm-pack build:

  1. Компилирует ваш Rust-код в WebAssembly.
  2. Запускает wasm-bindgen с этим WebAssembly, генерируя JavaScript файл, который оборачивает WebAssembly файл в модуль. который может понять npm.
  3. Создает папку pkg, куда перемещает этот JavaScript файл и ваш код WebAssembly.
  4. Читает ваш Cargo.toml и создает эквивалентный package.json.
  5. Копирует ваш README.md (если есть) в пакет.

Конечный результат? У вас есть npm-пакет внутри папки pkg.

Отступление о размере кода

Если вы посмотрите на размер кода, сгенерированного для WebAssembly, это может быть около сотни килобайт. Мы вообще не инструктировали Rust оптимизировать размер, и он сильно его снизил. Это не является частью этого руководства, но если вам интересно, прочитайте документацию Rust WebAssembly Working Group на Shrinking .wasm Size.

Публикация нашего пакета на npm

Давайте опубликуем наш новый пакет на npm:

$ cd pkg
$ npm publish --access=public

Теперь у нас есть npm-пакет, написанный на Rust, но скомпилированный в WebAssembly. Он готов к использованию из JavaScript, и его пользователь не нуждается в установке Rust; код внутри пакета написан на WebAssembly, не на Rust!

Использование пакета в web

Давайте создадим сайт, который будет использовать наш пакет! Многие пользуются пакетами npm с помощью разных сборщиков, и мы будем использовать один из них, webpack, в этом руководстве. Он только немного более усложненный, но описывает более реалистичный вариант использования.

Давайте выйдем из нашей папки pkg и создадим новую, site, чтобы попробовать в ней следующее:

$ cd ../..
$ mkdir site
$ cd site

Создайте новый файл, package.json, и поместите в него следующий код:

{
  "scripts": {
    "serve": "webpack-dev-server"
  },
  "dependencies": {
    "@mynpmusername/hello-wasm": "^0.1.0"
  },
  "devDependencies": {
    "webpack": "^4.25.1",
    "webpack-cli": "^3.1.2",
    "webpack-dev-server": "^3.1.10"
  }
}

Заметьте, что вам нужно ввести свое пользовательское имя после @ в секции зависемостей.

Дальше нам нужно сконфигурировать Webpack. Создайте webpack.config.js и введите следующее:

const path = require('path');
module.exports = {
  entry: "./index.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "index.js",
  },
  mode: "development"
};

Теперь нам нужен HTML-файл; создайте index.html и поместите в него:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>hello-wasm example</title>
  </head>
  <body>
    <script src="./index.js"></script>
  </body>
</html>

Наконец, создайте index.js, на который мы сослались в HTML-файле, и вставьте:

const js = import("./node_modules/@yournpmusername/hello-wasm/hello_wasm.js");
js.then(js => {
  js.greet("WebAssembly");
});

Заметьте, что вам нужно будет снова ввести ваше имя для npm.

Так мы импортируем наш модуль из папки node_modules. Это не считается лучшей практикой, но это пример, так что пока сойдет. Как только файл загрузится, он вызовет функцию greet из этого модуля, передав "WebAssembly", как строку. Обратите внимание, что здесь нет ничего особенного, и все же мы вызываем код на Rust! Насколько JavaScript код может судить, это просто обычный модуль.

Мы закончили! Давайте попробуем:

$ npm install
$ npm run serve

Так мы запустим сервер. Откройте http://localhost:8080 и вы увидете алерт с надписью Hello, WebAssembly!  в нем! Мы успешно обратились из JavaScript в Rust и из Rust в JavaScript.

Conclusion

This is the end of our tutorial; we hope you've found it useful.

There's lots of exciting work going on in this space; if you'd like to help make it even better, check out the Rust Webassembly Working Group.

Метки документа и участники

Внесли вклад в эту страницу: curdwithraisins, mdnwebdocs-bot, VLDSLW
Обновлялась последний раз: curdwithraisins,