本文为原Repo的Readme翻译。翻译时间:2018/01/22
Rust + WebAssembly: 初步整合
本Repo意在简单有机地整合Rust和WebAssembly的初步工作。
Key WebAssembly 背景
WebAssembly 是一种简单机器模型下的执行格式,其具有一个扩展规范
WebAssembly 并未和 JS 或者 Web 绑定;也没有预设其宿主环境。 因此也有理由假设 wasm 将会成为重要的跨环境“可移植执行体”格式。 可以这么说:今时今日,wasm 的确和 JS 关系重大,这是因为收到了多方的青睐(浏览器和 Node.js)。
WebAssembly 只有很小的一个值类型集合,基本上限制在简单数值的范围内。
WebAssembly 具有非常简单的内存模型。 目前,wasm 可以接入一个非常简单的“线性内存”,其基本上就是一块扁平的定长数字型数组。 这个内存可以以一个页大小的倍数增长,但不能缩减(shrink)。
状态
Rust 编译器
Rust 编译器目前支持两个 wasm 关联的目标(target):
-
wasm32-unknown-unknown
。此目标直接使用 llvm 后端编译成 wasm。 它适合纯 rust 代码编译,譬如你没有 C 依赖的时候。 跟 emscripten 目标比起来,它默认就生成更加洗练的代码, 而且也便于设置搭建。此处查看如何设置搭建. -
wasm32-unknown-emscripten
。此目标利用 emscripten 工具链编译成 wasm。 当你具有 C 依赖的时候就得使用它了,包括 libc。此处查看如何设置搭建.
wasm32-unknown-unknown
十分有望将新生的 Rust 代码融入 JS 项目中。然而,它也是相对欠成熟的后端:
- 它 只支持优化编译.
- 它 要求编译时使用单个的大型编译单元.
- 部分增强被限制,rustc 的 LLVM 远远滞后.
Rust 标准库
每个 wasm 目标对于 std
都有段不同的故事:
-
对于
wasm32-unknown-unknown
,Rust直接将自己那个很小的分配器用在 wasm 的页分配器上。 也就是说,所有alloc
级别的 API(例如:所有的容器类型)都可用,而其他只存在于std
上的 API —— 例如 thread,network,file,process —— 对于此目标都不可用。所有目前 API 虽然可见,但是 panic 和 error 其实都不可用。 我们计划将来将这些 API 为这个目标单独cfg
出来。随着 wasm spec 的发展,这些附加的 API 可能会回来(特别是 thread)。
-
对于
wasm32-unknown-emscripten
,Rust 使用 emscripten 工具来来提供基于 libc 的功能。 也就是说,有大量的std
可用,但是会导致明显的二进制文件膨胀。
JS 互操作
导入导出 JS 函数
从 Rust 侧角度
在 JS 宿主环境中运行 wasm 时,导入导出 Rust 函数十分直接:跟 C 的工作机制几乎一样。如下:
// 导入 JS 函数 `foo`
extern { fn foo(); }
// 导出 Rust 函数 `bar`
#[no_mangle]
pub extern fn bar() { /* ... */ }
因为 wasm 有限的值类型,这些函数只能操作原生数值类型。
从 JS 侧角度
JS 中的 wasm 二进制转换成 ES6 模块,其必须先使用一段线性内存和一组符合导入期望的 JS 函数来实例化。实例化详情请见 MDN。
其生成的 ES6 模块将会包含所有 Rust 导出的函数,可以像 JS 函数一样使用。
这 是一个简单的案例,包含了整个设置。
在数值之上
当和 JS 一起使用 wasm 的时候,wasm 模块的内存和 JS 内存之间有明显的界限:
-
每个 wasm 模块都有一段线性内存(本文档顶部有描述), 其在实例化期间初始化。JS 代码可以随意读写这段内存
-
相反,wasm 代码无法直接接触 JS 对象。
因此产生了两种相当复杂互操作:
-
将二进制数据拷入拷出 wasm 的内存。例如将一个 owned
String
传给 Rust 侧。 -
搭建一个显式的 JS 对象堆到给定“地址”。这使 wasm 可以简介引用 JS 对象(使用整数),然后使用导入的 JS 函数操作这些对象。
有幸的是,这种互操作使用一种“bindgen”风格框架(wasm-bindgen)也能经得住考验。这个框架使得可以编写常规的 Rust 函数签名,然后自动映射为常规的 JS函数。
JS 包生态
目前我们主要说的是函数层面的互操作,但实际生产中,我们经常也要与包一级互操作,亦即是生成或者消费 npm 包。
此部分目前处于 设计阶段,有以下约束:
-
基于 Rust/wasm 的包,其消费者无需意识到使用了 Rust。即是使用这种包的时候,不需要本地 Rust 工具链。
- 也就是以二进制的形式发布到 npm:我们上传一个 Rust 代码完全编译了的
.wasm
文件。
- 也就是以二进制的形式发布到 npm:我们上传一个 Rust 代码完全编译了的
-
你可以在库的 Rust 部分使用标准 Cargo 工作流来工作。
-
应该有种方式来描述 Rust/wasm 项目的 npm 元数据(如
package.json
的内容)。- 也就是说,Rust 项目可能会拉取自己的 crates,每个 crates 又会拉拉自己的 npm 依赖包。
-
应该有某种方便的方式来发布这类项目到 npm,处理所有需要的相关依赖。
-
最后,JS 打包器(例如 WebPack 和 Parcel)需要理解基于 wasm 的 npm 包,并生成合适的模块实例化。
- 此方向的工作正在进行中.
如果你有意协助开发,请进入此 tracking issue!
DOM,GC 集成以及其他
目前存在一些困惑,wasm 代码是否能和 DOM 互通,或者这样会不会明显阻碍 GC 的集成。
需要澄清的是:wasm 当然能和 DOM 互通。你可以使用如同 wasm-bindgen 的策略操作 DOM 并通过 JS 调用回来。然而这样却引入了性能损失,因此通过批量处理 DOM 操作可以提升效率。譬如 changelist proposal 这种对 DOM 的提升,和 Host Bindings proposal 折中对 WebAssembly 的提升,将为 DOM 互通操作铺平道路。
crate 生态
crates.io 上已经有了 wasm 的初期生态,其中比较优秀的有:
Demo,演讲和其他
- 多个 Rust 为中心的资源可以在找到 https://www.hellorust.com/ ,包含 demo,演讲,以及追踪关于 Rust 和 wasm 显著成就的新闻流。
- 其外还有很多常规 wasm 资源:
- http://webassembly.org/
- https://github.com/mbasso/awesome-wasm
- http://wasmweekly.news/
评论区
写评论66666
终于翻译完了……