而现在, rust 语言已经具备比较完善的 WebAssembly 支持,相关工具链也已经比较完善。我们可以通过这种方式在小程序中嵌入 rust 语言编写的模块。这样:
性能优于普通 JavaScript ;
二进制级别的反编译难度,反编译小程序代码将变得非常困难;
如同 rust 的强大静态检查能力,充分提升代码质量。
我制作了一个示例来说明如何在小程序中嵌入 rust 模块:
https://github.com/LastLeaf/miniprogram-rust-wasm-example
接下来我来详细解释一下其中的步骤方法。
创建 rust 模块
首先,在小程序的一个子目录中初始化一个 rust 模块(需要比较新的 rust 版本):
$ cargo init --lib
复制
更改项目配置 Cargo.toml ,加上几项:
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
wasm-bindgen = "0.2"
[profile.release]
opt-level = "z"
lto = true
复制
其中, crate-type 中的 cdylib 表示它可以编译为 WebAssembly 模块, rlib 表示它可以被其他 rust 模块引用。opt-level 表示优化等级(类似于 -Oz 编译参数), lto 表示 Link-Time-Optimization ,这两项配置可以降低编译后代码尺寸。
之后,在 src/lib.rs 中添加一小段测试代码。下面这段代码是一个 a + b 函数:
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn sum(a: i32, b: i32) -> i32 {
a + b
}
复制
基本代码就准备好了。
编译 rust 模块
编译到 WebAssembly 需要 wasm-pack 工具:
https://github.com/rustwasm/wasm-pack
$ wasm-pack build
复制
编译后的代码位于 pkg 目录中。编译出来的这组文件中,有两个文件比较重要:一个 xxx_bg.wasm 文件包含 rust 模块的编译结果,一个 xxx.js 文件作为对这个 wasm 的接口封装。
小程序无法直接运行 wasm 模块文件。不过,使用 wasm2js 工具可以将 wasm 转换为等价的 JavaScript ,使之可以在小程序中运行。使用这种方式转换得到的 js 代码性能虽然比不上原始的 WebAssembly ,但由于它只使用较高效的 JavaScript 特性,所以相对传统手写的 JavaScript 有性能优势。
wasm2js 是 binaryen 工具集中的一个:
https://github.com/WebAssembly/binaryen/releases
下载整个 binaryen 之后,可以把其中的工具都放到 PATH 环境变量中,方便使用。
使用 wasm2js 将 wasm 转换为 js :
$ wasm2js -O pkg/xxx_bg.wasm.js pkg/xxx_bg.wasm
复制
这样会生成 pkg/xxx_bg.wasm.js 文件。
在其他 js 文件中可以引用 rust 模块导出的函数。注意,引用的是作为封装的 pkg/xxx.js 文件:
import { sum } from './pkg/xxx'
sum(1, 2) === 3 // true
复制
在开发者工具中运行时,需要在项目设置中开启 ES6 转 ES5 ,因为编译得到的文件都是 ES6 的。
引入 polyfill
然而,仅这样还不能在 iOS 设备上运行,因为 iOS 的 JS 环境缺少 atob 全局函数。如果 rust 的接口中包括字符串类型,则还会缺少 TextEncoder 和 TextDecoder 。这种情况下,需要手工填加这几个 polyfill 。我从几个开源模块中提取了这几个 polyfill 做成了一个小文件:
https://github.com/LastLeaf/miniprogram-rust-wasm-example/blob/master/polyfill.js
我推荐的引入方式是在 pkg/xxx.js 和 pkg/xxx_bg.wasm.js 的开头插入一个 import 语句来引入 polyfill (可以做一个 shell 脚本或 gulpfile 来自动做这个插入)。具体而言,在 pkg/xxx.js 开头插入:
import { TextDecoder, TextEncoder } from './polyfill';
复制
在 pkg/xxx_bg.wasm.js 的开头插入:
import { atob } from './polyfill';
复制
这样,就可以在 iOS 手机上运行了!
附注
完整示例地址:
https://github.com/LastLeaf/miniprogram-rust-wasm-example
上面这个示例仅展示了最简单的 WebAssembly 用例。事实上,可以编译为 wasm32 目标的 rust 项目几乎都可以这么做,导入函数、导出函数等等都可以,具体可以参考 wasm-bindgen 文档。
此外,因为 rust 可以导入 C 语言接口,所以可以把 rust 语言作为“桥”,来在小程序中引入 C/C++ 模块!(具体做法上需要一些 LLVM 技巧,这篇文章不太好详细叙述,之后我会考虑再写一篇文章来介绍。)
有任何问题想交流,可以留言给我,也可以在项目里发 issue !这个公众号是我的新公众号,欢迎关注、在看、转发三连 ~