这是使用 Rust 和 WebAssembly 的未发布文档,已发布的文档可在Rust 和 WebAssembly 主文档网站上找到。此处记录的功能可能在 Rust 和 WebAssembly 工具的已发布版本中不可用。

JavaScript 交互操作

导入和导出 JS 函数

从 Rust 端

在 JS 宿主中使用 wasm 时,从 Rust 端导入和导出函数非常简单:它与 C 非常相似。

WebAssembly 模块声明一系列导入,每个导入都有一个模块名称和一个导入名称extern { ... } 块的模块名称可以使用#[link(wasm_import_module)] 指定,目前默认为“env”。

导出只有一个名称。除了任何 extern 函数之外,WebAssembly 实例的默认线性内存被导出为“memory”。


# #![allow(unused_variables)]
#fn main() {
// import a JS function called `foo` from the module `mod`
#[link(wasm_import_module = "mod")]
extern { fn foo(); }

// export a Rust function called `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 内存。例如,这是一种将拥有 String 提供给 Rust 端的方式。

  • 设置一个显式的 JS 对象“堆”,然后为这些对象分配“地址”。这允许 wasm 代码间接地(使用整数)引用 JS 对象,并通过调用导入的 JS 函数来操作这些对象。

幸运的是,这种交互故事非常适合通过通用的“bindgen”风格框架进行处理:wasm-bindgen。该框架使编写映射到惯用 JS 函数的惯用 Rust 函数签名成为可能,并且是自动的。

自定义节

自定义节允许将命名任意数据嵌入到 wasm 模块中。节数据在编译时设置,并直接从 wasm 模块中读取,它不能在运行时修改。

在 Rust 中,自定义节是使用 #[link_section] 属性公开的静态数组 ([T; size])


# #![allow(unused_variables)]
#fn main() {
#[link_section = "hello"]
pub static SECTION: [u8; 24] = *b"This is a custom section";
#}

这将一个名为 hello 的自定义节添加到 wasm 文件中,Rust 变量名称 SECTION 是任意的,更改它不会改变行为。这里的内容是文本字节,但可以是任何任意数据。

可以使用 JS 端的WebAssembly.Module.customSections 函数读取自定义节,它接受 wasm 模块和节名称作为参数,并返回一个ArrayBuffer 数组。可以使用相同的名称指定多个节,在这种情况下,它们都将出现在此数组中。

WebAssembly.compileStreaming(fetch("sections.wasm"))
.then(mod => {
  const sections = WebAssembly.Module.customSections(mod, "hello");

  const decoder = new TextDecoder();
  const text = decoder.decode(sections[0]);

  console.log(text); // -> "This is a custom section"
});