Изготовление сайтов в Санкт-Петербурге. Учебное пособие по Rust: введение в Rust для разработчиков JavaScript

 
 

Rust — это язык программирования, созданный Mozilla Research в 2010 году. Сегодня его используют все крупные компании.

И Amazon, и Microsoft одобрили его как лучшую альтернативу C/C++ для своих систем. Но Rust не останавливается на достигнутом. Такие компании, как Figma и Discord, теперь лидируют, также используя Rust в своих клиентских приложениях.

Это руководство по Rust призвано дать краткий обзор Rust, как его использовать в браузере и когда вам следует подумать об его использовании. Я начну со сравнения Rust с JavaScript, а затем покажу вам, как настроить и запустить Rust в браузере. Наконец, я представлю краткую оценку производительности моего веб-приложения симулятора COVID, использующего Rust и JavaScript.

Коротко о ржавчине

Rust концептуально сильно отличается от JavaScript. Но есть и сходство, на которое стоит обратить внимание. Давайте посмотрим на обе стороны медали.

Сходства

Оба языка имеют современную систему управления пакетами. В JavaScript есть npm, в Rust — Cargo. Вместо package.json, Rust имеет Cargo.tomlдля управления зависимостями. Чтобы создать новый проект, используйте cargo init, а чтобы запустить его, используйте cargo run. Не слишком чужой, не так ли?

В Rust есть много интересных функций, которые вы уже знаете из JavaScript, только с немного другим синтаксисом. Возьмите этот распространенный шаблон JavaScript, чтобы применить замыкание к каждому элементу в массиве:

let staff = [

{name: «George», money: 0},

{name: «Lea», money: 500000},

];

let salary = 1000;

staff.forEach ((employee) => { employee.money += salary; }) ;

В Rust мы бы написали это так:

let salary = 1000;

staff.iter_mut ().for_each (

|employee| { employee.money += salary; }

) ;

По общему признанию, требуется время, чтобы привыкнуть к этому синтаксису с вертикальной чертой (|), заменяющей круглые скобки.

Но после того, как я преодолел первоначальную неловкость, мне стало легче читать, чем еще один набор скобок.

В качестве другого примера, вот деструктуризация объекта в JavaScript:

let point = { x: 5, y: 10 };

let {x, y} = point;

Аналогично в Rust:

let point = Point { x: 5, y: 10 };

let Point { x, y } = point;

Основное отличие состоит в том, что в Rust мы должны указывать тип (Point). В общем, Rust должен знать все типы во время компиляции. Но, в отличие от большинства других компилируемых языков, компилятор выводит типы самостоятельно, когда это возможно.

Чтобы объяснить это немного подробнее, вот код, который работает на C++ и многих других языках. Каждая переменная нуждается в явном объявлении типа:

int a = 5;

float b = 0.5;

float c = 1.5 * a;

В JavaScript, как и в Rust, этот код действителен:

let a = 5;

let b = 0.5;

let c = 1.5 * a;

Список общих функций можно продолжать и продолжать:

Rust имеет синтаксис async+.await

Массивы можно создавать так же легко, как let array = [1,2,3].

Код организован в модули с явным импортом и экспортом.

Строковые литералы закодированы в Unicode и без проблем обрабатывают специальные символы.

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

Отличия

Rust — компилируемый язык, а это означает, что нет среды выполнения, которая выполняет код Rust. Приложение может работать только после того, как компилятор (rustc) сделал свое волшебство. Преимуществом такого подхода обычно является более высокая производительность.

К счастью, Cargo позаботится о вызове компилятора вместо нас. А с помощью webpack мы также сможем спрятаться cargoза npm run build. С помощью этого руководства можно сохранить обычный рабочий процесс веб-разработчика после настройки Rust для проекта.

Rust — строго типизированный язык, а это означает, что все типы должны совпадать во время компиляции. Например, вы не можете вызвать функцию с параметрами неправильного типа или с неправильным количеством параметров. Компилятор поймает ошибку за вас до того, как вы столкнетесь с ней во время выполнения. Очевидное сравнение — TypeScript. Если вам нравится TypeScript, то, скорее всего, вам понравится и Rust.

Но не волнуйтесь: если вам не нравится TypeScript, Rust все еще может вам подойти. В последние годы Rust создавался с нуля с учетом всего, что человечество узнало о проектировании языков программирования за последние несколько десятилетий. В результате получается освежающе чистый язык.

Сопоставление с образцом в Rust — моя любимая функция. В других языках есть switchи caseчтобы избежать длинных цепочек, подобных этой:

if (x == 1) {

//...

} else if (x == 2) {

//...

}

else if (x == 3 || x == 4) {

//...

} //...

Rust использует более элегантный способ match, который работает следующим образом:

match x {

1 => { /* Do something if x == 1 */},

2 => { /* Do something if x == 2 */},

3 | 4 => { /* Do something if x == 3 || x == 4 */},

5... 10 => { /* Do something if x ≥ 5 && x ≤ 10 */},

_ => { /* Catch all other cases */ }

}

Я думаю, что это довольно удобно, и я надеюсь, что разработчики JavaScript также оценят это расширение синтаксиса.

К сожалению, нам также приходится говорить о темной стороне Rust. Скажем прямо, использование строгой системы типов иногда может показаться очень громоздким. Если вы думали, что системы типов C++ или Java строгие, приготовьтесь к тяжелому путешествию с Rust.

Лично мне нравится эта часть о Rust. Я полагаюсь на строгость системы типов и, таким образом, могу отключить часть своего мозга — часть, которая сильно покалывает каждый раз, когда я ловлю себя на том, что пишу на JavaScript. Но я понимаю, что новичкам может быть очень неприятно постоянно бороться с компилятором. Мы увидим некоторые из них позже в этом уроке по Rust.

привет ржавчина

Теперь давайте запустим hello worldRust в браузере. Начнем с того, что установим все необходимые инструменты.

Инструменты

Установите Cargo + rustc с помощью rustup. Rustup — рекомендуемый способ установки Rust. Он установит компилятор (rustc) и менеджер пакетов (Cargo) для новейшей стабильной версии Rust. Он также может управлять бета-версиями и ночными версиями, но в этом примере это не обязательно.

Проверьте установку, набрав cargo —versionтерминал. Вы должны увидеть что-то вроде cargo 1.48.0 (65cbdd2dc 2020-10-14).

Также проверьте Rustup: rustup —versionдолжно получиться rustup 1.23.0 (00924c9ba 2020-11-27).

Установите wasm-pack. Это нужно для интеграции компилятора с npm.

Проверьте установку, набрав wasm-pack —version, что должно дать вам что-то вроде wasm-pack 0.9.1.

Нам также нужны Node и npm. У нас есть полная статья, в которой объясняется, как лучше всего установить эти два.

Написание кода на Rust

Теперь, когда все установлено, давайте создадим проект. Окончательный код также доступен в этом репозитории GitHub. Мы начинаем с проекта Rust, который можно скомпилировать в пакет npm. Код JavaScript, который импортирует этот пакет, появится позже.

Чтобы создать проект Rust с именем hello-world, используйте cargo init —lib hello-world. Это создает новый каталог и генерирует все файлы, необходимые для библиотеки Rust:

├──hello-world

├── Cargo.toml

├── src

├── lib.rs

Код Rust будет находиться внутри lib.rs. Перед этим мы должны настроить Cargo.toml. Он определяет зависимости и другую информацию о пакете с помощью TOML. Для приветствия в браузере добавьте следующие строки где-нибудь в свой Cargo.toml (например, в конец файла):

[

lib

]

crate-type = ["cdylib"]

Это говорит компилятору создать библиотеку в режиме C-совместимости. Очевидно, что в нашем примере мы не используем C. C-совместимый просто означает не специфичный для Rust, а это то, что нам нужно для использования библиотеки из JavaScript.

Также нам понадобятся две внешние библиотеки. Добавьте их отдельными строками в раздел зависимостей:

[

dependencies

]

wasm-bindgen = «0.2.68»

web-sys = {version = «0.3.45», features = ["console"]}

Это зависимости от crates.io, репозитория пакетов по умолчанию, который использует Cargo.

wasm-bindgen необходим для создания точки входа, которую мы впоследствии можем вызывать из JavaScript. (Вы можете найти полную документацию здесь.) Значение «0.2.68»указывает версию.

web-sys содержит привязки Rust ко всем веб-API. Это даст нам доступ к консоли браузера. Обратите внимание, что мы должны явно выбрать функцию консоли. Наш окончательный двоичный файл будет содержать только выбранные таким образом привязки веб-API.

Далее идет фактический код внутри lib.rs. Автоматически сгенерированный модульный тест можно удалить. Просто замените содержимое файла этим кодом:

use wasm_bindgen: prelude: *;

use web_sys: console;

#[wasm_bindgen]

pub fn hello_world () {

console: log_1 («Hello world») ;

}

Операторы useвверху предназначены для импорта элементов из других модулей. (Это похоже на importJavaScript.)

pub fn hello_world () {... }объявляет функцию. Модификатор pubявляется сокращением от «public» и действует как exportв JavaScript. Аннотация #[wasm_bindgen]специфична для компиляции Rust в WebAssembly (Wasm). Нам это нужно здесь, чтобы убедиться, что компилятор предоставляет функцию-оболочку для JavaScript.

В теле функции на консоль выводится «Hello world». console: log_1 () в Rust — это оболочка для вызова console.log (). (Подробнее читайте здесь.)

Вы заметили _1суффикс при вызове функции? Это связано с тем, что JavaScript допускает переменное количество параметров, а Rust — нет. Чтобы обойти это, wasm_bindgenгенерирует одну функцию для каждого количества параметров. Да, это может быстро стать уродливым! Но это работает. Полный список функций, которые можно вызывать на консоли изнутри Rust, доступен в документации web-sys.

Теперь у нас должно быть все на месте. Попробуйте скомпилировать его с помощью следующей команды. Это загружает все зависимости и компилирует проект. В первый раз это может занять некоторое время:

cd hello-world

wasm-pack build

Хм! Компилятор Rust нами недоволен:

error[E0308]: mismatched types

—> src\lib.rs:6:20

|

6 | console: log_1 («Hello world») ;

| ^^^^^^^^^^^^^ expected struct `JsValue`, found `str`

|

= note: expected reference `&JsValue`

found reference `&‘static str

Примечание. Если вы видите другую ошибку (error: linking with cc failed: exit code: 1) и работаете в Linux, у вас отсутствуют зависимости от кросс-компиляции. sudo apt install gcc-multilibдолжен решить это.

Как я упоминал ранее, компилятор строгий. Когда он ожидает ссылку на a JsValueв качестве аргумента функции, он не примет статическую строку. Явное преобразование необходимо для удовлетворения требований компилятора.

console: log_1 (&«Hello world».into ());

Метод into () преобразует одно значение в другое. Компилятор Rust достаточно умен, чтобы отложить, какие типы участвуют в преобразовании, поскольку сигнатура функции оставляет только одну возможность. В этом случае он будет преобразован в JsValue, который является оболочкой для значения, управляемого JavaScript. Затем мы также должны добавить, &чтобы передавать его по ссылке, а не по значению, иначе компилятор снова будет жаловаться.

Попробуйте запустить wasm-pack buildснова. Если все пойдет хорошо, последняя напечатанная строка должна выглядеть так:

[INFO]: -) Your wasm pkg is ready to publish at /home/username/intro-to-rust/hello-world/pkg.

Если вам удалось зайти так далеко, теперь вы можете компилировать Rust вручную. Далее мы интегрируем это с npm и webpack, которые сделают это за нас автоматически.

Интеграция с JavaScript

Для этого примера я решил разместить package.jsonвнутри hello-worldкаталог. Мы также могли бы использовать разные каталоги для проекта Rust и проекта JavaScript. Это дело вкуса.

Ниже мой package.jsonфайл. Самый простой способ — скопировать его и запустить npm install. Или запустите npm initи скопируйте только зависимости разработчика:

{

«name»: «hello-world»,

«version»: «1.0.0»,

«description»: «Hello world app for Rust in the browser.»,

«main»: «index.js»,

«scripts»: {

«build»: «webpack»,

«serve»: «webpack serve»

},

«author»: «Jakob Meier »,@jakobmeier.ch>

«license»: «(MIT OR Apache-2.0)»,

«devDependencies»: {

«@wasm-tool/wasm-pack-plugin»: «~1.3.1»,

«@webpack-cli/serve»: «^1.1.0»,

«css-loader»: «^5.0.1»,

«style-loader»: «^2.0.0»,

«webpack»: «~5.8.0»,

«webpack-cli»: «~4.2.0»,

«webpack-dev-server»: «~3.11.0»

}

}

Как видите, мы используем webpack 5. Wasm-pack также работает со старыми версиями webpack или даже без сборщика. Но каждая установка работает немного по-своему. Я бы посоветовал вам использовать точно такие же версии при следовании этому руководству по Rust.

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

Двигаясь дальше, нам также нужно создать webpack.config.jsфайл для настройки webpack. Вот как это должно выглядеть:

const path = require (‘path’) ;

const webpack = require (‘webpack’) ;

const WasmPackPlugin = require («@wasm-tool/wasm-pack-plugin») ;

module.exports = {

entry: '. /src/index.js’,

output: {

path: path.resolve (__dirname, ‘dist’),

filename: ‘index.js’,

},

plugins: [

new WasmPackPlugin ({

crateDirectory: path.resolve (__dirname, «.»)

}),

],

devServer: {

contentBase: «. /src»,

hot: true,

},

module: {

rules: [{

test: /\.css$/i,

use: [«style-loader», «css-loader»],

}, ]

},

experiments: {

syncWebAssembly: true,

},

};

Все пути настроены так, чтобы код Rust и код JavaScript находились рядом. Будет index.jsв srcпапке рядом с lib.rs. Не стесняйтесь настраивать их, если вы предпочитаете другую настройку.

Вы также заметите, что мы используем эксперименты с веб-пакетами — новый параметр, представленный в веб-пакете 5. Убедитесь, что syncWebAssemblyдля него установлено значение true.

Наконец, мы должны создать точку входа JavaScript src/index.js:

import («.../pkg»).catch (e => console.error («Failed loading Wasm module:», e)).then (

rust =>

rust.hello_world ()

) ;

Мы должны загрузить модуль Rust асинхронно. Вызов rust.hello_world () вызовет сгенерированную функцию-оболочку, которая, в свою очередь, вызовет функцию Rust, hello_worldопределенную в lib.rs.

Запуск npm run serveтеперь должен скомпилировать все и запустить сервер разработки. Мы не определили файл HTML, поэтому на странице ничего не отображается. Вам, вероятно, также придется перейти на http: //localhost:8080/index вручную, так как http: //localhost:8080 просто перечисляет файлы без выполнения какого-либо кода.

Получив пустую страницу, откройте консоль разработчика. Должна быть запись в журнале с Hello World.

Хорошо, это было довольно много работы для простого hello world. Но теперь, когда все готово, мы можем легко расширить код Rust, не беспокоясь ни о чем из этого. После сохранения изменений в lib.rs, вы должны автоматически увидеть перекомпиляцию и живое обновление в браузере, как и в случае с JavaScript!

Когда использовать ржавчину

Rust не является универсальной заменой JavaScript. Он может работать только в браузере через Wasm, и это немного ограничивает его полезность. Несмотря на то, что вы могли бы заменить практически весь код JavaScript на Rust, если бы действительно захотели, это плохая идея, а не то, для чего был создан Wasm. Например, Rust не подходит для взаимодействия с пользовательским интерфейсом вашего веб-сайта.

Я думаю о Rust + Wasm как о дополнительной опции, которую можно использовать для более эффективного запуска рабочей нагрузки с высокой нагрузкой на процессор. За счет большего размера загрузки Wasm позволяет избежать накладных расходов на синтаксический анализ и компиляцию, с которыми сталкивается код JavaScript. Это, а также серьезная оптимизация компилятором потенциально приводит к повышению производительности. Обычно именно поэтому компании выбирают Rust для конкретных проектов. Еще одной причиной выбора Rust могут быть языковые предпочтения. Но это совсем другая дискуссия, в которую я не буду вдаваться.

Симулятор коронной инфекции

Пришло время создать настоящее приложение, раскрывающее истинную мощь Rust в браузере! Ниже представлена ​​живая демонстрация CodePen. Если вы предпочитаете смотреть его в полном размере, нажмите здесь. Также доступен репозиторий с кодом.

Сетка внизу содержит ячейки, которые являются нашими объектами моделирования. Красный квадрат представляет клетку, зараженную вирусом. Нажимая на отдельные ячейки, вы можете добавлять или удалять инфекции.

Кнопка «Начать симуляцию «имитирует заражение между клетками с небольшой задержкой между каждым днем ​​симуляции. Нажатие кнопки с надписью «Следующий день «имитирует один день.

Правила заражения просты. Здоровая клетка имеет определенный шанс заразиться за каждого зараженного соседа внутри радиуса заражения. После фиксированного количества дней заражения клетка восстанавливается и становится невосприимчивой до конца симуляции. Существует также определенный шанс, что зараженная клетка умирает каждый день между заражением и выздоровлением.

Форма в верхней правой части управляет различными параметрами моделирования. Они могут быть обновлены в любое время и вступят в силу на следующий день. Если вы измените размер сетки, симуляция сбросится.

Большая часть того, что вы видите, реализована на JavaScript. Только при нажатии на «Следующий день» или «Начать симуляцию «код Rust будет вызываться для расчета всех заражений. Я также реализовал эквивалентный интерфейс на JavaScript, чтобы позже мы могли легко провести сравнение производительности. Ползунок, переключающийся между Rust и JS, изменяется между двумя реализациями. Даже при запуске автоматизированной симуляции это вступает в силу немедленно (на следующий день).

Интерфейс моделирования

Объект моделирования (реализованный на Rust и JavaScript) предоставляет только один класс с конструктором и двумя дополнительными методами. Сначала я дам вам нотацию JavaScript:

export class JsSimulation {

constructor () {

/* implementation omitted */

}

reconfigure (

w,

h,

radius,

recovery,

infection_rate,

death_rate,

) {

/* implementation omitted */

}

next_day (input, output) {

/* implementation omitted */

}

}

Конструктор не принимает аргументов. Он просто установит для всех параметров конфигурации значения по умолчанию. Используя reconfigure (), значения могут быть установлены на значения из HTML-формы. Наконец, next_day () принимает вход Uint8Arrayи выход Uint8Array.

В Rust класс определяется следующим образом:

#[wasm_bindgen]

pub struct Simulation { /* fields omitted */ }

В Rust нет понятия классов. Есть только struct. Определение, struct Simulationкак указано выше, с #[wasm_bindgen]аннотацией создаст класс-оболочку JavaScript, который можно использовать в качестве прокси.

Чтобы определить методы в структуре, мы используем implблок в Rust. Таких блоков для структуры может быть много, разбросанных по разным файлам. В этом примере нам нужен только один из них:

#[wasm_bindgen]

impl Simulation {

#[wasm_bindgen (constructor) ]

pub fn new () → Simulation {

/* implementation omitted */

}

pub fn reconfigure (

&mut self,

w: usize,

h: usize,

radius: usize,

recovery: u8,

infection_rate: f32,

death_rate: f32,

) {

/* implementation omitted */

}

pub fn next_day (&mut self, input: &[u8], output: &mut [u8]) {

/* implementation omitted */

}

}

Самое большое отличие от JavaScript заключается в том, что все типы явно упоминаются в сигнатурах функций. Например, w: usizeозначает, что параметр wимеет тип usize, который представляет собой целое число без знака, размер которого соответствует естественному размеру целевой архитектуры (например, 64-разрядной). Простой u8, с другой стороны, представляет собой 8-битное целое число без знака на всех платформах.

В функции каждый из next_day () двух параметров input: &[u8], output: &mut [u8]ожидает срезu8, который по существу является массивом. Заметили mutключевое слово в типе вывода? Это сообщает компилятору, что мы собираемся изменить содержимое массива, тогда как из входного массива мы будем только читать. (Компилятор остановит нас, если мы попытаемся мутировать input.)

Есть еще такой странный &mut selfпараметр. Это эквивалент thisв JavaScript. Если метод изменяет внутреннее состояние объекта, это должно быть указано явным добавлением &mut selfв список параметров. Если вы только читаете из поля объекта, но не делаете мутаций, &selfэто тоже нормально. Если вообще нет доступа к полям объекта, вам не нужно упоминать self.

Для типа возвращаемого значения функции Rust использует обозначение стрелки (→). То fn new () → Simulationже самое и с функцией, которая возвращает Simulationобъект. А поскольку я добавил #[wasm_bindgen (constructor) ]аннотацию, эта функция будет вызываться, когда код JavaScript вызывает конструктор класса-оболочки, как в let sim = new Simulation ().

Представление статуса заражения

Входной и выходной массивы используют однобайтовое целое число без знака для представления статуса заражения каждой ячейки. Нам нужно каким-то образом закодировать всю необходимую информацию о ячейке внутри этого байта.

В JavaScript я определил несколько констант для представления различных возможностей:

const HEALTHY = 0;

const INFECTED_DAY_0 = 1;

const INFECTED_DAY_MAX = 64;

const IMMUNE = 100;

const DEAD = 101;

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

В Rust мы можем сделать то же самое, enumнапример:

enum InfectionStatus {

Healthy,

Infected (u8),

Immune,

Dead,

}

Значение type InfectionStatusможет принимать любое из указанных выше значений. Rust будет присваивать значениям уникальные номера, и нам, программистам, не нужно об этом беспокоиться. Кроме того, перечисления в Rust более гибкие, чем перечисления в C. Для значения варианта доступно Infectedдополнительное связанное число типа u8, представляющее количество дней, в течение которых клетка была заражена.

Реализация ржавчины

Давайте посмотрим на реализацию fn next_day () в Rust. Он начинается с двух вложенных forциклов для прохождения всех ячеек сетки:

for x in 0...self.w {

for y in 0...self.h {

/*...more code... */

}

}

В этом примере xидет от 0до self.w (исключительно self.w). Две точки между двумя значениями создают диапазон целых чисел, по которым проходит цикл for. Вот как forпишутся циклы в Rust: какая-то переменная xперебирает что-то итерируемое. В этом случае что-то итерируемое — это диапазон чисел. Точно так же мы могли бы перебирать значения массива (for element in slice { /* do something with element */ }).

Заглянув внутрь тела цикла, он начинается с поиска во входном массиве:

let current = self.get (input, x, y) ;

Функция getопределена ниже в lib.rs. Он считывает соответствующее u8значение из входного массива и преобразует его в InfectionStatusсоответствии с константами, определенными в JavaScript. Таким образом, currentпеременная имеет тип InfectionStatus, и мы можем использовать для нее мощное сопоставление с образцом Rust:

let current = self.get (input, x, y) ;

let mut next = current;

match current {

Healthy => {

if self.chance_to_catch_covid_today (input, x, y) > self.rng.gen () {

next = Infected (1) ;

}

}

Infected (days) => {

if self.death_rate > self.rng.gen () {

next = Dead;

} else if days ≥ self.recovery {

next = Immune;

} else {

next = Infected (days + 1) ;

}

}

Dead | Immune => { /* NOP */ }

}

self.set (output, x, y, next) ;

Хорошо, это много кода. Позвольте мне сломать его. Во- первых, мы устанавливаем nextто же значение, что и текущее. (По умолчанию статус вывода должен быть таким же, как и входной статус.) Затем match currentблок переключается между возможностями для текущего статуса заражения.

Если ячейка сейчас здорова, мы вычисляем вероятность заражения сегодня в отдельной функции, которая будет перебирать всех соседей в настроенном радиусе. (Подробности здесь опущены.) Эта функция возвращает значение между 0.0и 1.0, которое мы сравниваем со случайным числом в том же диапазоне, сгенерированным на месте. Если шанс заразиться сегодня выше, чем случайное число, мы устанавливаем nextзначение Infected (1), что означает, что это первый день заражения клетки. (Примечание: в Rust мы обычно опускаем круглые скобки вокруг ifусловия.)

Если ячейка уже заражена, мы проверяем две вещи. Во-первых, умрет ли клетка сегодня, опять же на основе случайного числа, сгенерированного на месте. Если ячейке не повезло, nextустанавливается в Dead. 😢 В противном случае проверяем, не наступила ли дата восстановления, в этом случае новый статус будет Immune. Наконец, если ни одна из двух проверок не была положительной, счетчик дней заражения увеличивается на единицу.

Последний вариант сопоставления с образцом заключается в том, что клетка уже мертва или невосприимчива. В обоих этих случаях мы ничего не делаем.

После выражения соответствия мы вызываем функцию установки, которая принимает InfectionStatusзначение. Он преобразует значение обратно в a u8и записывает его в выходной массив. Я не буду вдаваться в подробности реализации этого здесь, но я рекомендую вам взглянуть на исходный код, если вам интересно.

И это все! Этот цикл выполняется один раз для каждого дня моделирования. Ничего больше.

Сравнительные результаты

И последний вопрос: стоило ли вообще использовать здесь Rust? Чтобы ответить на этот вопрос, я выбрал пару настроек и сравнил скорость реализации на Rust со скоростью на JavaScript. Что вы ожидаете? Ответ может вас удивить.

При настройках по умолчанию (100 ячеек, радиус заражения = 2) первый день симуляции занимает в среднем 0,11 мс на Rust и 0,72 мс на JavaScript. Таким образом, Rust более чем в шесть раз быстрее! Но по мере того, как я увеличивал размер и продолжал моделирование в течение нескольких дней моделирования, JavaScript внезапно выполнял ту же работу в два раза быстрее, чем Rust.

Ниже приведены графики экспериментов с большим количеством ячеек и измененным радиусом, что увеличивает общую рабочую нагрузку. Я выполнил этот тест на своем настольном ПК.

Диаграмма, показывающая время моделирования в течение первых 10 дней на настольном компьютере

Диаграмма показывает, что Rust значительно быстрее, чем JavaScript в первый день моделирования, независимо от настроек. Это когда код JavaScript должен быть проанализирован, скомпилирован и оптимизирован. После этого код JavaScript с каждым днем ​​становится все быстрее, так как JIT-компилятор оптимизирует ветки. Версия Rust сохраняет сравнительно стабильное время выполнения в течение всего дня.

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

Для дальнейшего исследования я провел те же тесты на своем Samsung Galaxy S7. Я предполагаю, что компилятор JIT, встроенный в мобильные браузеры, будет менее агрессивным, что усложнит эффективное выполнение кода JavaScript.

Диаграмма, показывающая время моделирования в течение первых 10 дней на мобильных устройствах

Действительно, результаты на моем телефоне были гораздо больше в пользу Rust на моем мобильном телефоне! В первый день моделирования с 3000 узлами версия на Rust была в 36,9 раза быстрее (1,45 мс против 53,5 мс)! Начиная с четвертого дня, эксперименты с 3000 и 10 000 узлов достигли относительно стабильной производительности для JavaScript. Но даже там Rust был быстрее в 2,5−3 раза (около 28 мс против 79 мс для 10 000 узлов).

Хотя приложение моделирования COVID не является репрезентативным образцом всех приложений Wasm, оно уже показывает, что выбор между реализацией Wasm и JavaScript зависит от многих факторов. Wasm может быть намного быстрее в правильных обстоятельствах и, как правило, более последователен, чем его аналог JavaScript. Но без тщательного бенчмаркинга трудно предсказать, какой из двух будет работать лучше на устройстве конечного пользователя.

Выполнение необходимых тестов на самом деле может быть довольно простым — если вы написали быстрый прототип Rust, который хотите сравнить с существующей реализацией JavaScript. Вы хотели бы измерить, сколько времени занимает выполнение кода Rust, включая накладные расходы на вызовы из JavaScript. Это можно очень легко измерить, используя только JavaScript:

const t0 = performance.now () ;

wasm.expensiveCall () ;

const t1 = performance.now () ;

console.log (`Rust code executed in ${t1 — t0}ms`) ;

В этом уроке по Rust я сделал именно это. Версия, развернутая в демо-версии CodePen, также связанной ранее, по-прежнему содержит код для эталонного тестирования. Вы можете просто открыть консоль разработчика, чтобы прочитать время, которое вы получаете на своем устройстве.

Заключение и дополнительные ресурсы

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

С помощью приложения для моделирования COVID я продемонстрировал, как создать полноценное приложение с помощью Rust в браузере. Чтобы количественно оценить производительность Rust, я также реализовал полное приложение на JavaScript и провел несколько тестов. Оценка производительности была немного в пользу JavaScript на десктопе, но явно в пользу Rust на мобильных устройствах. Главный вывод заключается в том, что только бенчмаркинг может точно сказать, какой язык быстрее для вашего приложения.

В этом уроке я также немного рассказал о Rust в целом. Rust — сложный для изучения язык, поэтому я намеренно не стал вдаваться в подробности этого введения. Если вы действительно хотите изучить Rust, я настоятельно рекомендую книгу Rust и руководство Rust by example в качестве основных источников.

Спасибо, что дочитали до конца! Если вам понравился этот учебник по Rust, вам также могут понравиться материалы, которые я разместил в своем личном блоге. Будьте моим гостем и посмотрите, как Rust встречается с Интернетом — столкновение парадигм программирования для более критического взгляда на Rust в браузере.

Готовы ли вы запрыгнуть в поезд Rust, пока он еще разгоняется?

3D-печать5GABC-анализAndroidAppleAppStoreAsusCall-центрChatGPTCRMDellDNSDrupalExcelFacebookFMCGGoogleHuaweiInstagramiPhoneLinkedInLinuxMagentoMicrosoftNvidiaOpenCartPlayStationPOS материалPPC-специалистRuTubeSamsungSEO-услугиSMMSnapchatSonyStarlinkTikTokTwitterUbuntuUp-saleViasatVPNWhatsAppWindowsWordPressXiaomiYouTubeZoomАвдеевкаАктивные продажиАкцияАлександровск ЛНРАлмазнаяАлчевскАмвросиевкаАнализ конкурентовАнализ продажАнтимерчандайзингАнтрацитАртемовскАртемовск ЛНРАссортиментная политикаБелгородБелицкоеБелозерскоеБердянскБизнес-идеи (стартапы)БрендБрянкаБукингВахрушевоВендорВидеоВикипедияВирусная рекламаВирусный маркетингВладивостокВнутренние продажиВнутренний маркетингВолгоградВолновахаВоронежГорловкаГорнякГорскоеДебальцевоДебиторкаДебиторская задолженностьДезинтермедитацияДзержинскДивизионная система управленияДизайнДимитровДирект-маркетингДисконтДистрибьюторДистрибьюцияДобропольеДокучаевскДоменДружковкаЕкатеринбургЕнакиевоЖдановкаЗапорожьеЗимогорьеЗолотоеЗоринскЗугрэсИжевскИловайскИрминоКазаньКалининградКировскКировскоеКомсомольскоеКонстантиновкаКонтент-маркетингКонтент-планКопирайтингКраматорскКрасноармейскКрасногоровкаКраснодарКраснодонКраснопартизанскКрасный ЛиманКрасный ЛучКременнаяКураховоКурскЛисичанскЛуганскЛутугиноМакеевкаМариупольМаркетингМаркетинговая информацияМаркетинговые исследованияМаркетинговый каналМаркетинг услугМаркетологМарьинкаМедиаМелекиноМелитопольМенеджментМерчандайзерМерчандайзингМиусинскМолодогвардейскМоскваМоспиноНижний НовгородНиколаевНиколаевкаНишевой маркетингНовоазовскНовогродовкаНоводружескНовосибирскНумерическая дистрибьюцияОдессаОмскОтдел маркетингаПартизанский маркетингПервомайскПеревальскПетровскоеПлата за кликПоисковая оптимизацияПопаснаяПравило ПаретоПривольеПрогнозирование продажПродвижение сайтов в ДонецкеПроизводство видеоПромоПромоушнПрямой маркетингРабота для маркетологаРабота для студентаРазработка приложенийРаспродажаРегиональные продажиРекламаРеклама на асфальтеРемаркетингРетро-бонусРибейтРитейлРовенькиРодинскоеРостов-на-ДонуРубежноеСамараСанкт-ПетербургСаратовСватовоСвердловскСветлодарскСвятогорскСевастопольСеверодонецкСеверскСедовоСейлз промоушнСелидовоСимферопольСинергияСколковоСлавянскСнежноеСоздание сайтов в ДонецкеСоледарСоциальные сетиСочиСтаробельскСтаробешевоСтахановСтимулирование сбытаСуходольскСчастьеТелемаркетингТельмановоТираспольТорговый представительТорезТрейд маркетингТрейд промоушнТюменьУглегорскУгледарУкраинскХабаровскХарцызскХерсонХостингЦелевая аудиторияЦифровой маркетингЧасов ЯрЧелябинскШахтерскЮжно-СахалинскЮнокоммунаровскЯндексЯсиноватая