这是我龙年的第一篇文章。首先,祝各位朋友龙年大吉!
我在 “WebAssembly 101” 一文中简单介绍了 WebAssembly 语言的背景和基础知识。我们说通常不会直接编写 WebAssembly 代码,而是由其它语言编译而来。例如,一个简单的求两个数之和的 WebAssembly 代码可能如下:
(module
(func (export "add") (param i32 i32) (result i32)
local.get 0
local.get 1
i32.add
)
)
由于语法表达力的不足(只提供了非常精简的指令),直接写的话比较繁琐。因此,最好使用表达力强的高级语言进行编码,而后由编译器生成 WebAssembly 代码。可供选择的编程语言有 C/C++、Rust、Go 等。在这众多语言中,Rust 对 WebAssembly 的全方位支持可能是最好的。Rust 语言是一门较新的编程语言,近年来一直倍受开发者喜爱。我之前写过一篇文章介绍了 Rust 语言:红红火火的 Rust 语言啊。下面,我们结合 Rust 来编写 WebAssembly 并在网页中查看效果。
对于上面两数求合的函数,在 Rust 中的代码实现如下:
#[wasm_bindgen]
pub fn add(x: f64, y: f64) -> f64 {
x + y
}
相比于 WebAssembly,这里的代码更简洁,也很容易读懂代码的意图。有一处特殊的地方是:#[wasm_bindgen]。这是 Rust 宏命令语法,它由 “wasm-bindgen” 代码包提供。它的意思是告诉编译器:如果需要的话,请帮我生成一些辅助代码,使得这个 Rust 语言中的函数可以在 WebAssembly 和 JavaScript 中使用。
为了使用 #[wasm_bindgen],我们需要先安装它。按如下修改 Rust 工程中的 Cargo.toml 文件:
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
编译 WebAssembly
我们使用 wasm-pack 工具来将 Rust 代码编译成 WebAssembly 代码,并同时生成 ESM 模块代码来方便在 JavaScript 中使用。在项目根目录运行:
wasm-pack build
会生成如下代码:
Rust 生成的 WebAssembly 被打包成了一个 npm 包,名为 “wasm-greet”。这个包对外导出了如下函数:
/**
* @param {number} x
* @param {number} y
* @returns {number}
*/
export function add(x: number, y: number): number;
此处的 add 函数是在 WebAssembly 中定义并导出的。在 JavaScript 中,可以用如下方式使用在 WebAssembly 中定义的 add 函数:
import { add } from 'wasm-greet';
alert(add(1,2));
效果如下:
总结
如果从纯代码的角度看整个编程体验,我们最初用 Rust 语言定义一个函数:
#[wasm_bindgen]
pub fn add(x: f64, y: f64) -> f64 {
x + y
}
然后,在 JavaScript 代码中用 ESM 的导入语句来导入这个函数:
import { add } from 'wasm-greet';
最后,就可以像使用普通 JavaScript 函数那样来使用 add 函数。
接下来
我们用只接收数值类型和返回数值类型的 add 函数举例是有意为之。因为在 WebAssembly 的底层(WASM 1.0 标准中),它只能直接处理 i32 ∣ i64 ∣ f32 ∣ f64 类型的值。那么,如何定义一个接受字符串类型的函数呢?或者如何定义一个接受 JavaScript 回调函数的函数呢?我们需要一些变通方法,用数值来间接表示复杂对象类型。例如,使用一个字符起始指针、一个字符串长度变量,再结合 WebAssembly 的线性内存 Memory 特性来间接传递字符串。这些更加复杂的场景才更是 wasm-bindgen + wasm-pack 的用武之地,它会帮我们生成这些辅助代码,而我们定义的函数只要声明接收字符串参数即可:
pub fn greet(name: &str) {}
让我们以后再聊这一部分。