WasmEdge объединяет производительность Rust и простоту использования JavaScript

В своей предыдущей статье я обсуждал, как запускать программы JavaScript в песочнице WebAssembly. Среда выполнения WasmEdge предоставляет легкий, высокопроизводительный и совместимый с OCI контейнер для облачных приложений JavaScript.

Однако JavaScript - медленный язык. Причина использования JavaScript в основном связана с его простотой использования и производительностью разработчика, особенно для начинающих разработчиков. WebAssembly, с другой стороны, способен запускать высокопроизводительные приложения, написанные на таких языках, как Rust. Есть ли решение, сочетающее в себе JavaScript и Rust? WasmEdge предлагает лучшее из обоих миров.

Благодаря WasmEdge и QuickJS разработчики теперь могут смешивать и сопоставлять эти два мощных языка для создания приложений. Это работает вот так.

  • Само приложение написано на Rust и скомпилировано в WebAssembly. Его можно скомпилировать и развернуть как один файл с байт-кодом wasm. Им можно управлять с помощью OCI-совместимых контейнерных инструментов, таких как Docker Hub, CRI-O и k8s.
  • Программы на JavaScript встроены в приложение Rust. Исходный код JavaScript может быть включен во время компиляции или во время выполнения через файл, STDIN, сетевой запрос или даже вызов функции. Это позволяет писать программу на JavaScript другим разработчиком, нежели программа на Rust.
  • Программа Rust могла обрабатывать ресурсоемкие задачи в приложении, а программа JavaScript могла обрабатывать «бизнес-логику». Например, программа Rust может подготовить данные для программы JavaScript.

Далее рассмотрим несколько примеров. Просмотрите репозиторий wasmedge-quickjs на Github и перейдите в папку examples/embed_js, чтобы следить за ним.

$ git clone https://github.com/second-state/wasmedge-quickjs
$ cd examples/embed_js

У вас должны быть установлены Rust и WasmEdge для сборки и запуска примеров из этой статьи.

Демо embed_js демонстрирует несколько различных примеров того, как встраивать JavaScript в Rust. Вы можете собрать и запустить все примеры следующим образом.

$ cargo build --target wasm32-wasi --release
$ wasmedge --dir .:. target/wasm32-wasi/release/embed_js.wasm

Привет, WasmEdge

Следующая функция Rust main.rs встраивает однострочную программу JavaScript во время компиляции.

fn js_hello(ctx: &mut Context) {
    let code = r#"print('hello quickjs')"#;
    let r = ctx.eval_global_str(code);
    println!("return value:{:?}", r);
}

Поскольку встроенная программа JavaScript ничего не возвращает, вы увидите следующий результат выполнения.

hello quickjs
return value:UnDefined

Возвращаемое значение

Конечно, встроенная программа JavaScript может вернуть значение программе Rust.

fn run_js_code(ctx: &mut Context) {
    let code = r#"
      let a = 1+1;
      print('js print: 1+1=',a);
      'hello'; // eval_return
    "#;
    let r = ctx.eval_global_str(code);
    println!("return value:{:?}", r);
}

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

js print: 1+1= 2
return value:String(JsString(hello))

Вызов встроенной функции JavaScript

Из программы Rust вы можете вызвать функцию JavaScript, передать параметры вызова и захватить возвращаемое значение. Этот первый пример показывает передачу строки Rust в функцию JavaScript.

fn run_js_function(ctx: &mut Context) {
    let code = r#"
      (x)=>{
          print("js print: x=",x)
      }
    "#;
    let r = ctx.eval_global_str(code);
    if let JsValue::Function(f) = r {
        let hello_str = ctx.new_string("hello");
        let mut argv = vec![hello_str.into()];
        let r = f.call(&mut argv);
        println!("return value:{:?}", r);
    }
    ...
}

Значение r из ctx.eval_global_str(code) - это функция Rust, которая отображается на функцию JavaScript. Вы можете вызвать функцию Rust и получить ожидаемые результаты от вызова функции JavaScript.

js print: x= hello
return value:UnDefined

Вы также можете передать массив из Rust в функцию JavaScript и позволить функции JavaScript возвращать значение.

fn run_js_function(ctx: &mut Context) {
    ...
    let code = r#"
      (x)=>{
          print("\nx=",x)
          let old_value = x[0]
          x[0] = 1
          return old_value
      }
    "#;
    let r = ctx.eval_global_str(code);
    if let JsValue::Function(f) = r {
        let mut x = ctx.new_array();
        x.set(0, 0.into());
        x.set(1, 1.into());
        x.set(2, 2.into());
        let mut argv = vec![x.into()];
        println!("argv = {:?}", argv);
        let r = f.call(&mut argv);
        println!("return value:{:?}", r);
    }
}

Результат следующий.

argv = [Array(JsArray(0,1,2))]
x= 0,1,2
return value:Int(0)

Используйте модули JavaScript

Встроенная программа JavaScript может загружать внешние файлы JavaScript как модули. Проект examples/embed_js_module демонстрирует этот вариант использования. Файл async_demo.js содержит модуль JavaScript с экспортированной функцией, которую мы затем импортируем и будем использовать во встроенном JavaScript.

import * as std from 'std'
async function simple_val (){
    return "abc"
}
export async function wait_simple_val (a){
    let x = await simple_val()
    print("wait_simple_val:",a,':',x)
    return 12345
}

Исходный код программы на Rust импортирует модуль async_demo.js асинхронно, как того требует спецификация ES. Результат выполнения p - это Rust Promise, который будет иметь правильное значение после возврата асинхронных функций. Вызов ctx.promise_loop_poll() в Rust ожидает завершения асинхронного выполнения. Затем мы можем получить возвращаемое значение от Promise.

use wasmedge_quickjs::*;
fn main() {
    let mut ctx = Context::new();
    let code = r#"
      import('async_demo.js').then((demo)=>{
        return demo.wait_simple_val(1)
      })
    "#;
    let p = ctx.eval_global_str(code);
    ctx.promise_loop_poll();
    println!("after poll:{:?}", p);
    if let JsValue::Promise(ref p) = p {
        let v = p.get_result();
        println!("v = {:?}", v);
    }
}

Результат следующий.

wait_simple_val: 1 : abc
after poll:Promise(JsPromise([object Promise]))
v = Int(12345)

Что дальше

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

Статьи из этой серии:

JavaScript в облачной WebAssembly все еще остается новой областью в инфраструктуре облачных и периферийных вычислений следующего поколения. Мы только начинаем! Если вам интересно, присоединяйтесь к нам в проекте WasmEdge (или сообщите нам, что вы хотите, поднимая вопросы о запросах функций).

Больше контента на plainenglish.io