暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

在微信小程序中使用 rust 语言编写模块(基于 WebAssembly )

最后的叶子的奇妙小屋 2021-07-27
4524
传统意义上,微信小程序的主逻辑只能使用 JavaScript 语言(或其衍生的 TypeScript 等语言)编写。这就不可回避 JavaScript 的一些固有问题。

而现在, 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 文件。

            引入 rust 模块中的函数

            在其他 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 !这个公众号是我的新公众号,欢迎关注、在看、转发三连 ~

                  文章转载自最后的叶子的奇妙小屋,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                  评论