用 Rust 创建高性能 JavaScript API

serverless 技术社区 2021-10-13 10:00:14
加精

WasmEdge 将 Rust 的性能和 JavaScript 的易用性结合在一起

在我之前的文章中,我讨论了如何将 JavaScript 代码嵌入到 Rust 程序。 然而,对于 JavaScript 开发者来说,需求往往相反——是将 Rust 函数合并到 JavaScript API 中。 这使开发者能够使用“纯 JavaScript”编写程序,同时仍然可以利用 Rust 函数的绝佳性能。 使用 WasmEdge Runtime ,你可以做到这一点。

接下来,让我们看几个示例。查看 wasmedge-quickjs Github repo 并打开 examples/embed_js 文件夹。

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

要先安装 RustWasmEdge 才能构建和运行本文中的示例。

embed_js demo 展示了几个关于如何在 Rust 中嵌入 JavaScript 的不同示例。 你可以按如下方式构建和运行所有示例。

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

创建一个 JavaScript 函数 API

以下代码片段定义了一个 Rust 函数,该函数可以作为 API 合并到 JavaScript 解释器中。

fn run_rust_function(ctx: &mut Context) {

    struct HelloFn;
    impl JsFn for HelloFn {
        fn call(_ctx: &mut Context, _this_val: JsValue, argv: &[JsValue]) -> JsValue {
            println!("hello from rust");
            println!("argv={:?}", argv);
            JsValue::UnDefined
        }
    }
    
    ...
}

下面的代码片段展示了如何将这个 Rust 函数添加到 JavaScript 解释器中,命名为 hi() 作为它的 JavaScript API,然后从 JavaScript 代码中调用它。

fn run_rust_function(ctx: &mut Context) {
    ...
    
    let f = ctx.new_function::<HelloFn>("hello");
    ctx.get_global().set("hi", f.into());
    let code = r#"hi(1,2,3)"#;
    let r = ctx.eval_global_str(code);
    println!("return value:{:?}", r);
}


执行结果如下:

hello from rust
argv=[Int(1), Int(2), Int(3)]
return value:UnDefined

使用这种方法,可以创建一个带有自定义 API 函数的 JavaScript 解释器。 解释器在 WasmEdge 内部运行,可以从 CLI 或网络执行调用此类 API 函数的 JavaScript 代码。

创建 JavaScript 对象 API

在 JavaScript API 设计中,我们有时需要提供一个同时封装数据和函数的对象。 在以下示例中,我们为 JavaScript API 定义了一个 Rust 函数。

fn rust_new_object_and_js_call(ctx: &mut Context) {

    struct ObjectFn;
    impl JsFn for ObjectFn {
        fn call(_ctx: &mut Context, this_val: JsValue, argv: &[JsValue]) -> JsValue {
            println!("hello from rust");
            println!("argv={:?}", argv);
            if let JsValue::Object(obj) = this_val {
                let obj_map = obj.to_map();
                println!("this={:#?}", obj_map);
            }
            JsValue::UnDefined
        }
    }
    
    

然后我们在 Rust 端创建一个“对象”,设置它的数据字段,然后将 Rust 函数注册为与对象关联的 JavaScript 函数。

    let mut obj = ctx.new_object();
    obj.set("a", 1.into());
    obj.set("b", ctx.new_string("abc").into());
    
    let f = ctx.new_function::<ObjectFn>("anything");
    obj.set("f", f.into());
    

接下来,我们使 Rust “对象”作为 JavaScript 对象 test_obj在 JavaScript 解释器中可用。

ctx.get_global().set("test_obj", obj.into());

在 JavaScript 代码中,你现在可以直接使用 test_obj 作为 API 的一部分。

let code = r#"
      print('test_obj keys=',Object.keys(test_obj))
      print('test_obj.a=',test_obj.a)
      print('test_obj.b=',test_obj.b)
      test_obj.f(1,2,3,"hi")
    "#;

    ctx.eval_global_str(code);
}

执行结果如下。

test_obj keys= a,b,f
test_obj.a= 1
test_obj.b= abc
hello from rust
argv=[Int(1), Int(2), Int(3), String(JsString(hi))]
this=Ok(
    {
        "a": Int(
            1,
        ),
        "b": String(
            JsString(
                abc,
            ),
        ),
        "f": Function(
            JsFunction(
                function anything() {
                    [native code]
                },
            ),
        ),
    },
)

一个完整的 JavaScript 对象 API

在前面的示例中,我们演示了从 Rust 创建 JavaScript API 的简单示例。 在这个例子中,我们将创建一个完整的 Rust 模块并将其作为 JavaScript 对象 API 提供。 该项目位于 examples/embed_rust_module 文件夹中。 你可以在 WasmEdge 中将其作为标准 Rust 应用程序构建和运行。

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

该对象的 Rust 实现是一个模块,如下所示。 它具有数据字段、构造函数、getter 和 setter 以及函数。

mod point {
    use wasmedge_quickjs::*;

    #[derive(Debug)]
    struct Point(i32, i32);

    struct PointDef;

    impl JsClassDef<Point> for PointDef {
        const CLASS_NAME: &'static str = "Point\0";
        const CONSTRUCTOR_ARGC: u8 = 2;

        fn constructor(_: &mut Context, argv: &[JsValue]) -> Option<Point> {
            println!("rust-> new Point {:?}", argv);
            let x = argv.get(0);
            let y = argv.get(1);
            if let ((Some(JsValue::Int(ref x)), Some(JsValue::Int(ref y)))) = (x, y) {
                Some(Point(*x, *y))
            } else {
                None
            }
        }

        fn proto_init(p: &mut JsClassProto<Point, PointDef>) {
            struct X;
            impl JsClassGetterSetter<Point> for X {
                const NAME: &'static str = "x\0";

                fn getter(_: &mut Context, this_val: &mut Point) -> JsValue {
                    println!("rust-> get x");
                    this_val.0.into()
                }

                fn setter(_: &mut Context, this_val: &mut Point, val: JsValue) {
                    println!("rust-> set x:{:?}", val);
                    if let JsValue::Int(x) = val {
                        this_val.0 = x
                    }
                }
            }

            struct Y;
            impl JsClassGetterSetter<Point> for Y {
                const NAME: &'static str = "y\0";

                fn getter(_: &mut Context, this_val: &mut Point) -> JsValue {
                    println!("rust-> get y");
                    this_val.1.into()
                }

                fn setter(_: &mut Context, this_val: &mut Point, val: JsValue) {
                    println!("rust-> set y:{:?}", val);
                    if let JsValue::Int(y) = val {
                        this_val.1 = y
                    }
                }
            }

            struct FnPrint;
            impl JsMethod<Point> for FnPrint {
                const NAME: &'static str = "pprint\0";
                const LEN: u8 = 0;

                fn call(_: &mut Context, this_val: &mut Point, _argv: &[JsValue]) -> JsValue {
                    println!("rust-> pprint: {:?}", this_val);
                    JsValue::Int(1)
                }
            }

            p.add_getter_setter(X);
            p.add_getter_setter(Y);
            p.add_function(FnPrint);
        }
    }

    struct PointModule;
    impl ModuleInit for PointModule {
        fn init_module(ctx: &mut Context, m: &mut JsModuleDef) {
            m.add_export("Point\0", PointDef::class_value(ctx));
        }
    }

    pub fn init_point_module(ctx: &mut Context) {
        ctx.register_class(PointDef);
        ctx.register_module("point\0", PointModule, &["Point\0"]);
    }
}

在解释器的实现中,首先调用 point::init_point_module 将 Rust 模块注册到 JavaScript 上下文中,然后我们就可以运行一个简单地使用 point 对象的 JavaScript 程序。

use wasmedge_quickjs::*;
fn main() {
    let mut ctx = Context::new();
    point::init_point_module(&mut ctx);

    let code = r#"
      import('point').then((point)=>{
        let p0 = new point.Point(1,2)
        print("js->",p0.x,p0.y)
        p0.pprint()
        try{
            let p = new point.Point()
            print("js-> p:",p)
            print("js->",p.x,p.y)
            p.x=2
            p.pprint()
        } catch(e) {
            print("An error has been caught");
            print(e)
        }    
      })
    "#;

    ctx.eval_global_str(code);
    ctx.promise_loop_poll();
}

上述应用程序的执行结果如下。

rust-> new Point [Int(1), Int(2)]
rust-> get x
rust-> get y
js-> 1 2
rust-> pprint: Point(1, 2)
rust-> new Point []
js-> p: undefined
An error has been caught
TypeError: cannot read property 'x' of undefined

接下来

使用 Rust 函数和模块来实现 JavaScript API 是一个强大的想法。 这允许 WasmEdge Runtime 显着提高 JavaScript 应用程序的性能。 然而,Rust 函数仍然需要编译成 WebAssembly 字节码。 对于一些函数,比如 AI 推理,直接从 WasmEdge 调用原生 C 库函数效率更高。 在下一篇文章中,我将讨论如何运用 WasmEdge 来支持 C 原生函数,然后将这些函数公开为 Rust 和 JavaScript API。

云原生 WebAssembly 中的 JavaScript 是下一代云和边缘计算基础设施中的新兴领域。 我们也是刚开始涉足。如果对此感兴趣,请加入 WasmEdge 项目或通过提出 feature request issue 告诉我们,你需要的是什么。

...全文
2158 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

217

社区成员

发帖
与我相关
我的任务
社区描述
Serverless 技术爱好者的聚集地
其他 企业社区
社区管理员
  • serverless 技术社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧