这篇文章将探索如何在Rust应用程序中嵌入Web Assembly代码。我们依赖于wasmtime,wasmtime是WebAssembly的一个独立运行时。
cargo new rustwasm
cd rustwasm
cargo add anyhow
cargo add wasmtime
use anyhow::Result;
use wasmtime::*;
fn main() -> Result<()> {
println!("Compiling module...");
let engine = Engine::default();
let module = Module::from_file(&engine, "hello.wat")?; //(1)
println!("Initializing...");
let mut store = Store::new(
&engine,
()
); //(2)
println!("Creating callback...");
let hello_func = Func::wrap(&mut store, |_caller: Caller<'_, ()>| {
println!("Calling back...");
}); //(3)
println!("Instantiating module...");
let imports = [hello_func.into()];
let instance = Instance::new(&mut store, &module, &imports)?;
println!("Extracting export...");
let run = instance.get_typed_func::<(), ()>(&mut store, "run")?; //(4)
println!("Calling export...");
run.call(&mut store, ())?; //(5)
println!("Done.");
Ok(())
}
我们从磁盘加载一个模块开始。我们正在装载hello.wat,默认情况下,将Web Assembly代码发布成二进制形式。然而,为了我们的目的,我们依赖于文本表示(wat)。
当运行wasm代码时我们还希望在host和wasm代码之间共享一些状态。通过Store,我们可以共享这个上下文。在我们的例子中,我们现在不需要共享任何状态。因此,我们指定一个Unit类型作为Store::new的第二个参数。
在本文中,我们想让一个Rust函数在wasm代码中可用,然后调用。我们用Func::wrap包装一个Rust闭包。这个函数不接受任何参数,而且也不返回任何东西;它只是在调用时打印出“Calling back”。
Wasm代码默认情况下没有主函数,我们把它当做lib。在我们的例子中,我们期望出现一个run函数,我们将使用它作为我们的入口点。
在调用函数之前,首先需要检索它。我们还使用get_typed_func来指定它的类型签名。如果我们在二进制文件中找到它,就可以调用它。
(module
(func $hello (import "" "hello"))
(func (export "run") (call $hello))
)
wasm文本格式使用lisp语言的s表达式。s表达式是一种非常古老且非常简单的表示树的文本格式,因此我们可以将模块视为描述模块结构及其代码的节点树。
与编程语言的抽象语法树不同,WebAssembly的树非常扁平,主要由指令列表组成。
它从宿主环境中导入一个名为hello的函数,并将其绑定到本地名称$hello。
它定义了一个run函数,并导出该函数。
Compiling module...
Initializing...
Creating callback...
Instantiating module...
Extracting export...
Calling export...
Calling back...
Done.