改进 javascript 和 rust 的互操作性:深入认识 wasm-凯发k8国际

引用
原文:javascript to rust and back again: a wasm-bindgen tale
链接:
译者:tocy, 琪花亿草, 雪落无痕xdj, 边城

最近我们已经见识了webassembly如何快速编译、加速js库以及生成更小的二进制格式。我们甚至为rust和javascript社区以及其他web编程语言之间的更好的互操作性制定了高级规划。正如前面一篇文章中提到的,我想深入了解一个特定组件的细节,wasm-bindgen。

今天webassembly标准只定义了四种类型:两种整数类型和两种浮点类型。然而,大多数情况下,js和rust开发人员正在使用更丰富的类型! 例如,js开发人员经常与互以添加或修改html节点相关的文档交互,而rust开发人员使用类似result等类型进行错误处理,几乎所有程序员都使用字符串。


被局限在仅使用由webassembly所提供的类型将会受到太多的限制,这就是wasm-bindgen出现的原因。


wasm-bindgen的目标是提供一个js和rust类型之间的桥接。它允许js使用字符串调用rust api,或rust函数捕获js异常。wasm-bindgen抹平了webassembly和javascript之间的阻抗失配,确保javascript可以高效地调用webassembly函数,并且无需boilerplate,同时webassembly可以对javascript函数执行相同的操作。

wasm-bindgen项目在其readme文件中有更多描述。要入门,让我们深入到一个使用wasm-bindgen的例子中,然后探索它还有提供了什么。

1、hello world!

学习新工具的最好也是最经典的方法之一就是探索下用它来输出“hello, world!”。在这里,我们将探索一个这样的例子——在页面里弹出“hello world!”提醒框。

这里的目标很简单,我们想要定义一个rust的函数,给定一个名字,它会在页面上创建一个对话框,上面写着hello,$name!在javascript中,我们可以将这个函数定义为:
export function greet(name) {
    alert(`hello, ${name}!`);
}

不过在这个例子里要注意的是,我们将把它用rust编写。这里已经发生了很多我们必须要处理的事情:
  • javascript将会调用一个webassembly 模块, 模块名是 greetexport.
  • rust函数将一个字符串作为输入参数,也就是我们要打招呼的名字。
  • 在内部rust会生成一个新的字符串,也就是传入的名字。
  • 最后rust会调用javascript的 alert函数,以刚创建的字符串作为参数。
启动第一步,我们创建一个新的rust工程:
$ cargo new wasm-greet --lib

这将初始化一个新的wasm-greet文件夹,我们的工作都在这里面完成。接下来我们要使用如下信息修改我们的cargo.toml(在rust里相当于package.json):
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"

我们先忽略[lib]节的内容,接下来的部分声明了对wasm-bindgen的依赖。这里的依赖包含了我们使用wasm-bindgen需要的所有的支持包。

接下来,是时候编写一些代码了!我们使用下列内容替换了自动创建的src/lib.rs:
#![feature(proc_macro, wasm_custom_section, wasm_import_module)]
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern {
    fn alert(s: &str);
}
#[wasm_bindgen]
pub fn greet(name: &str) {
    alert(&format!("hello, {}!", name));
}

如果你不熟悉rust,这可能看起来有点啰嗦,但不要害怕!随着时间的推移,wasm-bindgen项目不断改进,而且可以肯定的是,所有这些并不总是必要的。

要注意的最重要的一点是#[wasm_bindgen]属性,这是一个在rust代码中的注释,这里的意思是“请在必要时用wrapper处理这个”。我们对alert函数的导入和greet函数的导出都被标注为这个属性。稍后,我们将看到在引擎盖下发生了什么。

首先,我们从在浏览器中打开作为例子来切入正题!我们先编译wasm代码:
$ rustup target add wasm32-unknown-unknown --toolchain nightly # only needed once
$ cargo  nightly build --target wasm32-unknown-unknown

这段代码会生成一个wasm文件,路径为target/wasm32-unknown-unknown/debug/wasm_greet.wasm。如果我们使用工具如wasm2wat来看这个wasm文件里面的内容,可能会有点吓人。

结果发现这个wasm文件实际上还不能直接被js调用!为了能让我们使用,我们需要执行一个或更多步骤:
$ cargo install wasm-bindgen-cli # only needed once
$ wasm-bindgen target/wasm32-unknown-unknown/debug/wasm_greet.wasm --out-dir .

很多不可思议的事情发生都发生在这个步骤中:wasm-bindgen cli工具对输入的wasm文件做后期处理,使它变的“suitable”可用。

我们待会再来看“suitable”的意思,现在我们可以肯定的说,如果我们引入刚创建的wasm_greet.js文件(wasm-bindgen工具创建的),我们已经获取到了在rust中定义的greet函数。

最终我们接下来要做的是使用bundler对其打包,然后创建一个html页面运行我们的代码。

在写这篇文章的时候,只有webpack’s 4.0 release对webassembly的使用有足够的支持(尽管暂时已经有了 chrome caveat)。

总有一天,更多的bundler也会接着支持webassmbly。在这我不再描述细节,但是你可以看一下在github仓库里的example配置。不过如果我们看内容,这个页面中我们的js在看起来是这样的:
const rust = import("./wasm_greet");
rust.then(m => m.greet("world!"));

…就是这些了!现在打开我们的网页就会显示一个不错的“hello, world!”对话框,这就是rust驱动的。

2、wasm-bindgen是如何工作的
唷,那是一个巨大的“hello, world!”。让我们深入了解一下更多的细节,以了解后台发生了什么以及该工具是如何工作的。

wasm-bindgen最重要的方面之一就是它的集成基本上是建立在一个概念之上的,即一个wasm模块仅是另一种es模块。例如,在上述中我们想要一个带有如下签名的es模块(在typescript中):
export function greet(s: string);

webassembly无法在本地执行此操作(请记住,它目前只支持数字),所以我们依靠wasm-bindgen来填补空白。

在上述的最后一步中,当我们运行wasm-bindgen工具时,你会注意到wasm_greet.js文件与wasm_greet_bg.wasm文件一起出现。前者是我们想要的实际js接口,执行任何必要的处理以调用rust。* _bg.wasm文件包含实际的实现和我们所有的编译后的代码。

我们可以通过引入 ./wasm_greet 模块得到 rust 代码愿意暴露出来的东西。我们已经看到了是如何集成的,可以继续看看执行的结果如何。首先是我们的示例:
const rust = import("./wasm_greet");
rust.then(m => m.greet("world!"));

我们在这里以异步的方式导入接口,等待导入完成(下载和编译 wasm)。然后调用模块的 greet 函数。

注: 这里用到的异步加载目前需要 webpack 来实现,但总会不需要的。而且,其它打包工具可能没有此功能。

如果我们看看由 wasm-bindgen 工具为 wasm_greet.js 文件生成的内容,会看到像这样的代码:
import * as wasm from './wasm_greet_bg';
// ...
export function greet(arg0) {
    const [ptr0, len0] = passstringtowasm(arg0);
    try {
        const ret = wasm.greet(ptr0, len0);
        return ret;
    } finally {
        wasm.__wbindgen_free(ptr0, len0);
    }
}
export function __wbg_f_alert_alert_n(ptr0, len0) {
    // ...
}

注: 记住这是生成的,未经优化的代码,它可能既不优雅也不简洁!!在 rust 中通过 lto(link time optimization,连接时优化)创建新的发行版,再通过 js 打包工具流程(压缩)之后,可能会精简一些。

现在可以了解如何使用wasm-bindgen来生成greet函数。在底层它仍然调用wasm的greet函数,但是它是用一个指针和长度来调用的而不是用字符串。

了解passstringtowasm的更多细节可以访问lin clark’s previous post。它包含了所有的模板,对我们来说这是除了wasm-bindgen工具以外还需要去写的东西!然后我们接下来看__wbg_f_alert_alert_n函数。

进入更深一层,下一个我们感兴趣的就是webassmbly中的greet函数。为了了解这个,我们先来看rust编译器能访问到的代码。注意像上面生成的这种js wrapper,在这里你不用写greet的导出符号,#[wasm_bindgen]属性会生成一个shim,由它来为你翻译,命名如下:
pub fn greet(name: &str) {
    alert(&format!("hello, {}!", name));
}
#[export_name = "greet"]
pub extern fn __wasm_bindgen_generated_greet(arg0_ptr: *mut u8, arg0_len: usize) {
    let arg0 = unsafe { ::std::slice::from_raw_parts(arg0_ptr as *const u8, arg0_len) }
    let arg0 = unsafe { ::std::str::from_utf8_unchecked(arg0) };
    greet(arg0);
}

现在可以看到原始代码,greet,也就是由#[wasm_bindgen]属性插入的看起来有意思的函数__wasm_bindgen_generated_greet。这是一个导出函数(用#[export_name]和extern关键词来指定的),参数为js传进来的指针/长度对。在函数中它会将这个指针/长度转换为一个&str (rust中的一个字符串),然后将它传递给我们定义的greet函数。

从另一个方面看,#[wasm_bindgen]属性生成了两个wrappers:一个是在javascript中将js类型的转换为wasm,另外一个是在rust中接收wasm类型并将其转为rust类型。

现在我们来看wrappers的最后一块,即alert函数。rust中的greet函数使用标准format!宏来创建一个新的字符串然后传给alert。回想当我们声明alert方法的时候,我们是使用 #[wasm_bindgen]声明的,现在我们看看在这个函数中暴露给rustc的内容:
fn alert(s: &str) {
    #[wasm_import_module = "__wbindgen_placeholder__"]
    extern {
        fn __wbg_f_alert_alert_n(s_ptr: *const u8, s_len: usize);
    }
    unsafe {
        let s_ptr = s.as_ptr();
        let s_len = s.len();
        __wbg_f_alert_alert_n(s_ptr, s_len);
    }
}

这并不是我们写的,但是我们可以看看它是怎么变成这样的。alert函数事实上是一个简化的wrapper,它带有rust的 &str然后将它转换为wasm类型(数字)。它调用了我们在上面看到过的比较有意思的函数__wbg_f_alert_alert_n,然而它奇怪的一点就是#[wasm_import_module]属性。

在webassembly中所有导入的函数都有一个其存在的模块,而且由于wasm-bindgen构建在es模块之上,所以这也将被转译为es模块导入!

目前__wbindgen_placeholder__模块实际上并不存在,但它表示该导入将被wasm-bindgen工具重写,以从我们生成的js文件中导入。

最后,对于最后一部分的疑惑,我们得到了我们所生成的js文件,其中包含:
export function __wbg_f_alert_alert_n(ptr0, len0) {
    let arg0 = getstringfromwasm(ptr0, len0);
    alert(arg0)
}

哇! 事实证明,这里隐藏着相当多的东西,我们从js中的浏览器中的警告都有一个相对较长的知识链。不过,不要害怕,wasm-bindgen的核心是所有这些基础设施都被隐藏了! 你只需要在随便使用几个#[wasm_bindgen]编写rust代码即可。然后你的js可以像使用另一个js包或模块一样使用rust了。

wasm-bindgen还能做什么

wasm-bindgen项目在这个领域内志向远大,我们在此不再详细赘述。探索wasm-bindgen中的功能一个有效的方法就是探索示例目录,这些示例涵盖了从我们之前看到的hello world! 到在rust中对dom节点的完全操作。

wasm-bindgen高级特性如下:

  • 引入js结构,函数,对象等来在wasm中调用。你可以在一个结构中调用js方法,也可以访问属性,这给人一种rust是“原生”的感觉,让人觉得你曾经写过的rust #[wasm_bindgen] annotations都可以连接了起来。
  • 将rust结构和函数导出到js。与只用js使用数字类型来工作相比,你可以导出一个rust结构并在js中转换成一个类。然后可以将结构传递,而不是只使用整形数值来传递。 smorgasboard 这个例子可以让你体会支持的互操作特性。
  • 其他各种各样的特性例如从全局范围内导入(就像alert函数),在rust中使用一个result来获取js异常,以及在rust程序中通用方法模拟存储js值。
如果你想了解更多的功能,继续阅读 。

3、wasm-bindgen接下来做什么?

在我们结束之前,我想花一点时间来下描述wasm-bindgen的未来愿景,因为我认为这是当今项目最激动人心的一方面。

不仅仅支持rust

从第1天起,wasm-bindgen cli工具就设计成了多语言支持的。尽管rust目前是唯一被支持的语言,但该工具也可以嵌入c或c 。 #[wasm_bindgen]属性创建了可被wasm-bindgen工具解析并随后删除的输出(* .wasm)文件的自定义部分。

本节介绍要生成哪些js绑定以及它们的接口是什么。这个描述中没有关于rust的特定部分,因此c 编译器插件可以很容易地创建该部分,并通过wasm-bindgen工具进行处理。

我觉得这个方面特别令人振奋,因为我相信它使像wasm-bindgen这样的工具成为webassembly和js集成的标准做法。希望所有编译为webassembly的语言都能受益,并且可以被bundler自动识别,以避免上述几乎所有的配置和构建工具。

自动绑定js生态

使用#[wasm_bindgen] 宏导入功能唯一不好的一面就是你必须将所有东西都写出来,还要保证没有任何错误。这种让人觉得很单调(而且易错)的操作的自动化技术已经成熟了。

所有的web apis都由webidl指定,而且在generate #[wasm_bindgen] annotations from webidl是可行的。这个就意味着你不需要像前面一样定义alert函数,而是你只需要写下面这些:
#[wasm_bindgen]
pub fn greet(s: &str) {
    webapi::alert(&format!("hello, {}!", s));
}

在这个例子中,webidl对web apis的描述可以完全自动生成webapi集合,保证没有错误。

我们甚至可以将自动化更进一步,typescript组织已经做了这方面的复杂工作,参照generate #[wasm_bindgen] from typescript as well。可以免费用npm上的typescript自动绑定任何包!

比 js dom 操作更快的性能

最后要说的事情对 wasm-bindgen 来说也很重要:超快的 dom 操作 —— 这是很多 js 框架的终极目标。如今需要使用一些中间工具来调用 dom 函数,这些工具正在由 javascript 实现转向 c 引擎实现。然而,在 webassembly 来临之后,这些工具并非必须。webassembly 是有类型的。

从第一天起,wasm-bindgen 代码生成的设计就考虑到了将来的宿主绑定方案。当这一特征出现在 webassembly 之后,我们可以直接调用导入的函数,而不需要 wasm-bindgen 的中间工具。

此外,它使得 js 引擎积极优化 webassembly 对 dom 的操作,使其对类型的支持更好,而且在调用 js 的时候不再需要进行参数验证。在这一点上,wasm-bindgen 不仅在操作像 string 这样的富类型变得容易,还提供了一流的 dom 操作性能。

收工

我自己发现使用webassembly是异常令人振奋的,不仅仅是因为其社区,还因为其如此快速地在进度上突飞猛进。wasm-bindgen工具拥有光明的未来。它使js和诸如rust这样的编程语言之间的互操作性变成了一流的体验,并且随着webassembly的不断发展它也将提供了长期的好处。

试着给wasm-bindgen一次机会,因功能需求而创建一个问题,亦或继续保持参与rust和webassembly!

关于alex crichton(作者)
alex是rust核心团队的成员之一,自2012年底以来一直从事于rust。目前他正在帮助webassembly rust working group使得rust wasm成为最佳体验。alex还帮助维护cargo(rust的包管理器),rust标准库以及rust的发布和ci的基础架构。
来自:
0
0
评论 共 1 条 请登录后发表评论
1 楼 xxbb77 2018-11-12 16:32
非常感谢 谢谢分享

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • 主要介绍了改进 javascript 和 rust 的互操作性并深入认识 wasm-bindgen 组件,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

  • 学习webassembly,rust和node.js 由开源开发团队提供给您。 在 , , , 或上关注凯发k8国际娱乐官网入口。在node.js的服务器端 开发人员:为... 在wasm中执行数值计算的基本示例在wasm程序中操作字符串的示例 学习锈入门命令行输入和输出

  • 该工具旨在成为构建和使用生锈的webassembly的一站式商店,您希望在浏览器中或使用node.js与javascript互操作。wasm-pack帮助您构建生锈的webassembly包,您可以将其发布到npm注册表,或者与您已经使用的工作流中的...

  • webassembly(wasm)是一个简单的机器模型和可执行格式,具有广泛的规范。它被设计为便携、紧凑,代码执行能够达到接近本机原生指令的执行速度。作为一种编程语言,webassembly 由两种格式组成,它们以不同的方式...

  • 为了使构建过程与多个操作系统兼容,构建过程作为package.json的脚本存在。 有许多特殊标志用于将智能合约编译到 wasm 文件中。 运行此命令以构建 wasm 文件并将其放置在res目录中: npm run build 注意:代替npm ...

  • rust调用api 在为什么要在webassembly中使用rust? ,我探讨了为什么您可能要编写... 这是将rust与go,c#和其他语言(具有可编译为wasm的大型运行时)区分开来的功能。 rust的运行时最少(基本上只是一个分配器),...

  • 紫杉 rust / wasm客户端web应用程序框架 ... 支持javascript互操作性,从而使开发人员可以利用npm软件包并与现有javascript应用程序集成。 注意:yew尚未准备好生产,但非常适合辅助项目和内部工具

  • 支持 javascript 互操作性,允许开发人员利用 npm 包并与现有的 javascript 应用程序集成。注意:yew(尚未)生产就绪,但非常适合副项目和内部工具。贡献yew 是一项社区努力,我们欢迎来自各种背景的开发人员的各种...

  • webassembly (以下简称wasm)...由于 wasm 是静态类型,因此很难直接使用我们熟悉的 javascript来直接编写,目前的 wasm 都是通过其他静态语言编译而来。目前支持 wasm 的语言有 c 、rust、go等。其中 rust 对 wa...

  • 为了更好的了解 javascript 运行环境,以及能够通过 javascript 来实现更复杂的功能,比如渲染 webgl 渲染,数据可视化,游戏开发等,业界也提供了一些基于浏览器环境的 javascript 虚拟机,如 node.js,webkit jsc...

  • mindspore web组装后端 ... 加上的wasm端口 ,我们的新开源了所有的场景深深的学习框架,wasi可以使一个新的后端不可知的,高度安全,高性能的堆栈帮助用户和开发人员能够开发新的ai应用具有更好的便携性。 wasm还可以

  • 本文介绍 webassembly 的 javascript api。

  • finshir - 一种 low&slow 流量产生器 ...finshir 就是这样一种用rust写的工具。有两点高光: 使用了 may,对,就是黄旭东大佬的may协程库 可以配合 tor 使用,实现匿名性 repo 有下列特點 coroutines ...

  • 基于wasm的探索与研究(一)webassembly初探 本次分享的文章是基于webassembly的探索与研究。由于最近一直在做加密相关的项目,有想法把高级语言实现的加密工具运行在浏览器中,恰好webassembly是为了一个可移植的...

  • 如果 wasm(webassembly)和 wasi(webassembly system interface, wasm 系统接口)在 2008 年就已经存在,那就没有必要创建 docker 了。

  • ripgrep 11 发布ripgrep是linux命令行文件内容检索工具grep的rust实现版本。版本11修复了很多bug,改进了性能,对二进制文...

  • 简介先来说下在 webassembly(后续称wasm) 凯发k8国际娱乐官网入口官网上的介绍,主要有四点:高效:wasm 有一套完整的语义,实际上 wasm 是体积小且加载快的二进制格式, 其目标就是充分发挥硬件的能力以达到原生语言的执行效率安全:wasm ...

  • 由于web worker api非常强大,因此非常适合用作我们的插件的基准,例如,您可以编写访问各种操作系统api的本机插件或访问dom的仅web插件,但建议这样做您只能访问workers可用的功能,以最大程度地提高可移植性。...

  • 大厂技术坚持周更精选好文why rust在进行正式的分享之前,先来说一说为什么,要学习 rust 这一门在广义上归属于后端的语言,以及它能带给我们什么,未来有什么前景。与java...

  • 管理系统是一种通过计算机技术实现的用于组织、监控和控制各种活动的软件系统。这些系统通常被设计用来提高效率、减少错误、加强安全性,同时提供数据和信息支持。以下是一些常见类型的管理系统: 学校管理系统: 用于学校或教育机构的学生信息、教职员工信息、课程管理、成绩记录、考勤管理等。学校管理系统帮助提高学校的组织效率和信息管理水平。 人力资源管理系统(hrm): 用于处理组织内的人事信息,包括员工招聘、培训记录、薪资管理、绩效评估等。hrm系统有助于企业更有效地管理人力资源,提高员工的工作效率和满意度。 库存管理系统: 用于追踪和管理商品或原材料的库存。这种系统可以帮助企业避免库存过剩或不足的问题,提高供应链的效率。 客户关系管理系统(crm): 用于管理与客户之间的关系,包括客户信息、沟通记录、销售机会跟踪等。crm系统有助于企业更好地理解客户需求,提高客户满意度和保留率。 医院管理系统: 用于管理医院或医疗机构的患者信息、医生排班、药品库存等。这种系统可以提高医疗服务的质量和效率。 财务管理系统: 用于记录和管理组织的财务信息,包括会计凭证、财务报表、预算管理等。财务管理系统

global site tag (gtag.js) - google analytics
网站地图