wasm-bindgen 的设计

本节旨在深入探讨 `wasm-bindgen` 今天如何在内部工作,特别是对于 Rust 而言。如果你在未来很久之后阅读本文,它可能不再是最新的,但请随时提出问题,我们可以尝试回答问题和/或更新它!

基础:ES 模块

关于 `wasm-bindgen` 首先要了解的是,它从根本上建立在 ES 模块的思想之上。换句话说,这个工具采取了一个固执的立场,即 Wasm 文件*应该被视为 ES 模块*。这意味着你可以从 Wasm 文件 `import`,使用其 `export` 的功能等等,从普通的 JS 文件中。

现在不幸的是,在编写本文时,Wasm 互操作的接口不是很丰富。 Wasm 模块只能调用或导出仅处理 `i32`、`i64`、`f32` 和 `f64` 的函数。 真糟糕!

这就是这个项目发挥作用的地方。 `wasm-bindgen` 的目标是使用更丰富的类型(如类、JS 对象、Rust 结构体、字符串等)来增强 Wasm 模块的“ABI”。但请记住,一切都基于 ES 模块!这意味着编译器实际上正在生成某种“损坏”的 Wasm 文件。 例如,rustc 发出的 wasm 文件没有我们希望拥有的接口。 相反,它需要 `wasm-bindgen` 工具对文件进行后处理,生成 `foo.js` 和 `foo_bg.wasm` 文件。 `foo.js` 文件是以 JS 表达的所需接口(类、类型、字符串等),而 `foo_bg.wasm` 模块仅用作实现细节(它与原始 `foo.wasm` 文件略有不同)。

随着时间的推移,WebAssembly 中会稳定更多功能(如组件模型),预计 JS 文件会越来越小。 它不太可能永远消失,但 `wasm-bindgen` 旨在密切遵循 WebAssembly 规范和提案,以尽可能优化 JS/Rust。

基础 #2:在 Rust 中不突兀

在 Rust 方面,`wasm-bindgen` crate 的设计理念是在理想情况下尽可能减少对 Rust crate 的影响。 理想情况下,在关键位置注释一些 `#[wasm_bindgen]` 属性,否则你就可以开始使用了。 该属性力求既不发明新语法,又能与当今现有的习惯用法一起使用。

例如,一个库可能会在普通的 Rust 中公开一个函数,如下所示


# #![allow(unused_variables)]
#fn main() {
pub fn greet(name: &str) -> String {
    // ...
}
#}

使用 `#[wasm_bindgen]`,你需要做的就是将其导出到 JS


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
    // ...
}
#}

此外,此处在 Rust 中以最小的干预进行的设计应该使我们能够轻松利用即将推出的组件模型提案。 理想情况下,你只需升级 `wasm-bindgen`-the-crate 以及你的工具链,你就可以立即获得对组件模型的原始访问权限!(但这仍然需要一段时间……)