总有一天,更多的bundler也会接着支持WebAssmbly。在这我不再描述细节,但是你可以看一下在Github仓库里的example配置。不过如果我们看内容,这个页面中我们的JS在看起来是这样的:
代码
const rust = import("./wasm_greet"); rust.then(m => m.greet("World!"));
…就是这些了!现在打开我们的网页就会显示一个不错的“Hello, World!”对话框,这就是Rust驱动的。
2、wasm-bindgen是如何工作的
唷,那是一个巨大的“Hello, World!”。让我们深入了解一下更多的细节,以了解后台发生了什么以及该工具是如何工作的。
wasm-bindgen最重要的方面之一就是它的集成基本上是建立在一个概念之上的,即一个wasm模块仅是另一种ES模块。例如,在上述中我们想要一个带有如下签名的ES模块(在Typescript中):
代码
export function greet(s: string);
WebAssembly无法在本地执行此操作(请记住,它目前只支持数字),所以我们依靠wasm-bindgen来填补空白。
在上述的最后一步中,当我们运行wasm-bindgen工具时,你会注意到wasm_greet.js文件与wasm_greet_bg.wasm文件一起出现。前者是我们想要的实际JS接口,执行任何必要的处理以调用Rust。* _bg.wasm文件包含实际的实现和我们所有的编译后的代码。
我们可以通过引入 ./wasm_greet 模块得到 Rust 代码愿意暴露出来的东西。我们已经看到了是如何集成的,可以继续看看执行的结果如何。首先是我们的示例:
代码
const rust = import("./wasm_greet"); rust.then(m => m.greet("World!"));
我们在这里以异步的方式导入接口,等待导入完成(下载和编译 wasm)。然后调用模块的 greet 函数。
注: 这里用到的异步加载目前需要 Webpack 来实现,但总会不需要的。而且,其它打包工具可能没有此功能。
如果我们看看由 wasm-bindgen 工具为 wasm_greet.js 文件生成的内容,会看到像这样的代码:
代码
import * as wasm from './wasm_greet_bg'; // ... export function greet(arg0) { const [ptr0, len0] = passStringToWasm(arg0); try { const ret = wasm.greet(ptr0, len0); return ret; } finally { wasm.__wbindgen_free(ptr0, len0); } } export function __wbg_f_alert_alert_n(ptr0, len0) { // ... }
注: 记住这是生成的,未经优化的代码,它可能既不优雅也不简洁!!在 Rust 中通过 LTO(Link Time Optimization,连接时优化)创建新的发行版,再通过 JS 打包工具流程(压缩)之后,可能会精简一些。
现在可以了解如何使用wasm-bindgen来生成greet函数。在底层它仍然调用wasm的greet函数,但是它是用一个指针和长度来调用的而不是用字符串。
了解passStringToWasm的更多细节可以访问Lin Clark's previous post。它包含了所有的模板,对我们来说这是除了wasm-bindgen工具以外还需要去写的东西!然后我们接下来看__wbg_f_alert_alert_n函数。
进入更深一层,下一个我们感兴趣的就是WebAssmbly中的greet函数。为了了解这个,我们先来看Rust编译器能访问到的代码。注意像上面生成的这种JS wrapper,在这里你不用写greet的导出符号,#[wasm_bindgen]属性会生成一个shim,由它来为你翻译,命名如下:
代码
pub fn greet(name: &str) { alert(&format!("Hello, {}!", name)); } #[export_name = "greet"] pub extern fn __wasm_bindgen_generated_greet(arg0_ptr: *mut u8, arg0_len: usize) { let arg0 = unsafe { ::std::slice::from_raw_parts(arg0_ptr as *const u8, arg0_len) } let arg0 = unsafe { ::std::str::from_utf8_unchecked(arg0) }; greet(arg0); }
现在可以看到原始代码,greet,也就是由#[wasm_bindgen]属性插入的看起来有意思的函数__wasm_bindgen_generated_greet。这是一个导出函数(用#[export_name]和extern关键词来指定的),参数为JS传进来的指针/长度对。在函数中它会将这个指针/长度转换为一个&str (Rust中的一个字符串),然后将它传递给我们定义的greet函数。
从另一个方面看,#[wasm_bindgen]属性生成了两个wrappers:一个是在JavaScript中将JS类型的转换为wasm,另外一个是在Rust中接收wasm类型并将其转为Rust类型。