Rust на стороне web-интерфейса

Перевод | Автор оригинала: Nicolas Fränkel

До сих пор JavaScript был единственным широко распространенным языком, доступным в браузерах. Он сделал JavaScript намного более популярным, чем позволял его дизайн (и связанные с ним недостатки). Следовательно:

Я не хочу начинать священную войну по поводу достоинств JavaScript, но ИМХО, он выжил так далеко только благодаря своей роли в браузерах. В частности, в современных архитектурах ответственность за выполнение кода передается с сервера на клиента. Это создает большую нагрузку на последних. Существует не так много способов повысить производительность: либо купить более мощные (и дорогие!) Клиентские машины, либо улучшить движки JavaScript.

Приходит WebAssembly.

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

Wasm предназначен не для полной замены JavaScript в браузере (пока?), А для повышения общей производительности. Хотя Rust предназначен для системного программирования, он предлагает компиляцию для WebAssembly.

Это 5-й пост в серии статей о Start Rust.

  1. Моя первая чашка Rust
  2. Моя вторая чашка Rust
  3. Упражнения "Рустлингс" - часть 1.
  4. Упражнения "Рустлингс" - часть 2.
  5. Rust на интерфейсе (этот пост)
  6. Контроллер Rust для Kubernetes
  7. Rust и JVM
  8. diceroller, образец проекта на Rust.
  9. Вектор Rust

Rust и WebAssembly

Изучение Rust - долгий процесс; изучение Rust и WebAssembly тем более. К счастью, люди доброй воли уже немного облегчили этот путь. Они написали полностью бесплатное интерактивное учебное пособие, посвященное исключительно этой теме, доступное по дружественной лицензии с открытым исходным кодом.

Как ученик и тренер, я знаю, как сложно создать хороший учебник:

  1. Либо вы предоставляете пошаговый путь продвижения с решениями на этом пути, и это становится просто вопросом копирования и вставки.
  2. Или вы предоставляете что-то менее подробное, но вы рискуете, что некоторые учащиеся будут заблокированы и не смогут закончить.

Книга избегает обеих ловушек, поскольку следует первой схеме, но содержит дополнительные задачи в конце каждой главы. Чтобы не блокировать учащегося, в каждой проблеме содержится общая подсказка, которая поможет учащимся найти решение. Если вы не можете (или не хотите) решить конкретную проблему, вы все равно можете перейти к следующей главе. Обратите внимание, что в связанном репозитории Git каждая фиксация ссылается либо на стандартный шаг копирования и вставки, либо на проблему, которую необходимо решить.

Кроме того, в книге есть два раздела: собственное руководство и справочная часть. Таким образом, вы можете проверить соответствующие фрагменты документации во время обучения и углубить свое понимание после него.

Проект на Rust

Первый шаг руководства посвящен настройке. Он короткий и самый "копипастейный" из всех. Причина в том, что он использует Cargo-generate, плагин Cargo, который позволяет создавать новый проект, используя существующий репозиторий Git в качестве шаблона. В нашем случае шаблон представляет собой проект Rust, готовый к компиляции в Wasm. Структура проекта:

wasm-game-of-life/
├── Cargo.toml
└── src
    ├── lib.rs
    └── utils.rs

Это структура «стандартных» проектов на Rust. Сейчас отличное время для просмотра файла Cargo.toml. Он играет роль pom.xml, перечисляя метаинформацию о пакете, зависимостях, подсказках компиляции и т.д. Cargo.toml

[package]                                              
name = "wasm-game-of-life"
version = "0.1.0"
authors = ["Nicolas Frankel <nicolas@frankel.ch>"]
edition = "2018"

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

[features]
default = ["console_error_panic_hook"]

[dependencies]
wasm-bindgen = "0.2.63"                                

# Rest of the file omitted for clarity purposes
  1. Мета-информация о пакете
  2. Создайте библиотеку, а не двоичный файл
  3. Создайте как библиотеку Rust, так и динамическую системную библиотеку.
  4. Зависимость от производства Wasm

Интеграция внешнего интерфейса

Проект в нынешнем виде не очень интересен: волшебство не видно. Следующим шагом в руководстве является добавление веб-интерфейса для взаимодействия с кодом Rust, скомпилированным в Wasm.

Как и на предыдущем шаге, команда позволяет скопировать код из GitHub. Здесь команда - npm, а шаблон - create-wasm-app. Запустим команду:

npm init wasm-app www

Предыдущая команда выводит следующую структуру:

wasm-game-of-life/
└── www/
    ├── package.json                   
    ├── webpack.config.js               
    ├── index.js                       
    ├── bootstrap.js                   
    └── index.html
  1. Зеркальное отображение Cargo.toml для проектов NPM, настроенных для Wasm
  2. Конфигурация Webpack
  3. Основная точка входа в «приложение»: вызов кода Wasm.
  4. Оболочка асинхронного загрузчика для index.js

На этом этапе можно выполнить всю цепочку кода, если мы пройдем необходимые шаги сборки:

  1. Скомпилируйте код Rust в Wasm
  2. Сгенерируйте код адаптера JavaScript. Вы можете запустить этот и предыдущий этапы с помощью одного вызова wasm-pack. Проверьте сгенерированные файлы в папке pkg.
  3. Получите зависимости NPM с помощью npm install.
  4. Запустите локальный веб-сервер с npm run start.

При просмотре http: // localhost: 8080 должно отображаться простое сообщение alert().

В конце этого раздела упражнение предписывает вам заменить alert() на prompt(), чтобы обеспечить параметризацию. Вам следует соответствующим образом изменить код Rust и перекомпилировать его. Веб-сервер должен перезагружать новый код на лету, чтобы при обновлении страницы отображался обновленный код.

Моя идея, лежащая в основе этого поста, состоит не в том, чтобы переделывать весь урок, а в том, чтобы сосредоточиться на наиболее интересных частях. С Rust на интерфейсе все сводится к:

  1. Вызов Rust из JavaScript
  2. Вызов JavaScript из Rust
  3. Вызов API браузера из Rust

Вызов Rust из JavaScript

Чтобы вызвать Rust из JavaScript, вам нужно скомпилировать код Rust в Wasm и предоставить тонкую оболочку JavaScript. В шаблоне проекта он уже настроен. Вам нужно использовать макрос wasm-bindgen только для тех функций Rust, которые вы хотите сделать доступными.

#[wasm_bindgen]           
pub fn foo() {
    // do something
}
  1. Волшебный макрос!

Со стороны JavaScript:

import * as wasm from "hello-wasm-pack";       

wasm.foo();                                    
  1. Импортируйте все из пакета hello-wasm-pack в пространство имен wasm.
  2. Теперь вы можете вызвать foo()

Вызов JavaScript из Rust

Руководящий принцип, лежащий в основе учебника, - это «Игра жизни» Конвея. Один из способов инициализировать плату - это случайным образом установить каждую ячейку в мертвую или живую. Поскольку рандомизация должна происходить во время выполнения, нам нужно использовать JavaScript Math.random(). Следовательно, нам также необходимо вызывать функции JavaScript из Rust.

Базовая установка использует интерфейс внешних функций через ключевое слово extern:

#[wasm_bindgen]
extern "C" {                                 
    #[wasm_bindgen(js_namespace = Math)]     
    fn random() -> f64;
}

#[wasm_bindgen]
fn random_boolean() -> bool {
    random() < 0.5                           
}
  1. Это не код C, но в любом случае это правильный синтаксис
  2. Сгенерируйте интерфейс Rust, чтобы он мог компилироваться.
  3. Используйте это

Хотя это работает, оно очень подвержено ошибкам. В качестве альтернативы крэйт js-sys предоставляет все доступные привязки прямо из коробки:

Привязки к стандартным встроенным объектам JavaScript, включая их методы и свойства.Сюда не входят Web, Node или любые другие API-интерфейсы среды JS. Только то, что гарантировано стандартом ECMAScript в глобальной области видимости. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects

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

[dependencies]
js-sys = { version = "0.3.50", optional = true }  

[features]
default = ["js-sys"]                              
  1. Add the dependency as optional
  2. Activate the optional feature

Должен признать, что не понимаю, зачем устанавливать зависимость как необязательную и активировать ее в другой строке. Я пока оставлю это как есть.

Предыдущая конфигурация допускает следующий код:

use js_sys::Math;               

#[wasm_bindgen]
fn random_boolean() -> bool {
    Math::random() < 0.5        
}
  1. Используйте математику из крэйта js_sys
  2. Выполните компиляцию с помощью компилятора Rust и вызовите метод Math.random() JavaScript во время выполнения.

Вызов API браузера из Rust

крэйт js-sys позволяет нам вызывать API-интерфейсы JavaScript внутри кода Rust. Однако для вызова клиентских API, например console.log(), необходим крэйт web_sys.

Необработанные привязки API для веб-API.

Это процедурно сгенерированный крэйт из браузера WebIDL, который обеспечивает привязку ко всем API, которые браузеры предоставляют в Интернете. Этот крэйт по умолчанию содержит очень мало при компиляции, поскольку почти все его открытые API закрываются функциями Cargo. Исчерпывающий список функций можно найти в crates / web-sys / Cargo.toml, но практическое правило для web-sys состоит в том, что каждый тип имеет свою собственную функцию (названную в честь типа). Использование API требует включения функций для всех типов, используемых в API, и API должны указывать в документации, какие функции им требуются.

Вот как это настроить: Cargo.toml

[dependencies]
web-sys = { version = "0.3", features = ["console"] }

Мы можем использовать крэйт так:

// wasm.rs
use web_sys::console;                                 

#[wasm_bindgen]
impl Foo {
    pub fn new() -> Foo {
        utils::set_panic_hook();
        Universe {}
    }

    pub fn log(&self) {
        console::log_1("Hello from console".into());  
    }
}
  1. Требовать создания веб-системы. Я не уверен, нужен ли (или почему) extern.
  2. Воспользуйтесь консольным пакетом.
  3. console::log_1() преобразуется в console.log() с одним параметром во время выполнения

Вывод

В этом посте мы подробно описали три основных момента использования Rust в браузере: вызов Rust из JavaScript, вызов JavaScript из Rust и вызов API-интерфейсов браузера из Rust.

Следующее видео дает вам представление о конечном результате; Я могу только посоветовать вам попробовать это руководство самостоятельно. Полный исходный код этого поста можно найти на Github. Чтобы пойти дальше: