Я работаю с Javascript в качестве своей повседневной работы уже несколько лет.
В последнее время я начал возиться с Rust, чтобы изучить новые концепции и понять, что такое шумиха (см. Опрос Stack Overflow 2023 )
Похоже, в последнее время все больше и больше JS-инструментов пишут на Rust:
Зачем кому-то это делать?
- Javascript получил широкое распространение в отрасли, но производительность совсем невелика.
- Rust обладает убийственной производительностью, но ему сложнее научиться
Объединение их обоих — это способ получить лучшее из обоих слов: разрабатывать на простом для изучения и широко распространенном языке, Javascript, и оптимизировать важные части с помощью Rust.
Итак, как можно объединить Rust и Javascript в кодовой базе? Давайте посмотрим на напи-рс!
npx @napi-rs/cli new
Проект по умолчанию определяет следующий файл src/lib.rs
:
#![deny(clippy::all)] #[macro_use] extern crate napi_derive; #[napi] pub fn sum(a: i32, b: i32) -> i32 { a + b }
Запуск yarn build
создаст:
- Бинарный файл надстройки узла:
<project name>.<target>.node
index.js
, который определяет привязки JS для двоичного файла надстройки узла: он загружает правильный двоичный файл и экспортирует функциюsum
.index.d.ts
, который предоставляет определения типов дляindex.js
Использование функции sum
очень просто:
const { sum } = require("./index.js"); console.log(sum(40, 2));
Serde — это библиотека Rust, которая позволяет выполнять сериализацию и десериализацию из различных форматов данных, включая JSON.
Давайте сравним, насколько быстро мы можем работать с Serde + napi-rs по сравнению с парсингом JSON по умолчанию в JS.
Для этого теста мы будем анализировать объекты следующей формы:
{ name: string phoneNumbers: string[] }
Код Rust для функции parse
на основе Serde выглядит следующим образом:
#![deny(clippy::all)] #[macro_use] extern crate napi_derive; use serde::{Deserialize, Serialize}; #[napi(constructor)] #[derive(Serialize, Deserialize)] pub struct Person { pub name: String, pub phones: Vec<String>, } #[napi] pub fn parse(data: String) -> napi::Result<Person> { Ok(serde_json::from_str(&data)?) }
Настройка
Чтобы этот код заработал, вCargo.toml
необходимо выполнить некоторые настройки как наserde
, так и наnapi
:
Добавить
serde-json
в список функций дляnapi
:
napi = { ..., features = ["napi4","serde-json",] }
Добавьте функцию
derive
вserde
:
serde = { ..., features = ["derive"] }
Обработка ошибок
Rust и JS имеют очень разные системы обработки ошибок.
- Исключения для JS
- Тип результата для Rust
napi::Result<T>
позволяет автоматически превратить Rust Error в исключение JS.
Запуск функции parse
из файла JS с недопустимым JSON вызывает исключение:
const index = require("./index.js"); const parsedValue = index.parse("Invalid JSON"); console.log(parsedValue); const parsedValue = index.parse("Invalid JSON"); ^ Error: expected value at line 1 column 1 at ... { code: 'InvalidArg' }
parse
также выдает исключение, если полученные данные имеют неправильную форму:
const index = require("./index.js"); const parsedValue = index.parse('{"name": "John Doe"}'); console.log(parsedValue); const parsedValue = index.parse('{"name": "John Doe"}'); ^ Error: missing field `phones` at line 1 column 20 at ... { code: 'InvalidArg' }
Быстрый микротест
Поскольку Serde выполняет синтаксический анализ И проверку, вот функция parse
будет сравниваться с:
const yup = require("yup"); const schema = yup.object({ name: yup.string(), phones: yup.array(yup.string()), }); const parseJs = (data) => { const d = JSON.parse(data); return schema.validateSync(d); };
И эталонная функция:
const benchmark = (name, f, size) => { console.time(name); for (let i = 0; i < size; i++) { f(`{ "name": "John Doe", "phones": [ "+33 123456789" ] }`); } console.timeEnd(name); };
На 1М звонках результаты следующие:
JS: 8.356s Rust: 2.966s
Запуск тестов на 1000 итераций с объектами разного размера:
|`phones` array size|Rust (ms)|JS (ms)| |-------------------|---------|-------| | 1| 4,758| 51,946| | 10| 9,334| 41,266| | 100| 61,586|220,485| | 1000| 499,942| 1987| | 10000| 5338| 22685|
В этом тесте функция парсинга Rust работает в 4 раза быстрее, чем JS.
Заключение по этому эксперименту
Napi-rs позволяет легко использовать мощь Rust в кодовой базе Javascript:
- Экспорт функций из Rust не требует особых усилий, и их можно легко импортировать в Javascript.
- Похоже, что в этом есть выигрыш в производительности (хотя микротестам не следует уделять слишком много внимания).
- Для этой настройки требуется этап сборки, что делает ее менее удобной, чем настройку только для JS.
Код, который я использовал для этого эксперимента, можно найти в этом репозитории github
Первоначально опубликовано на сайте thinkful-fiddler.dev 30 июля 2023 г.