Deno 中的 Wasm 入门:从文本模块到 Rust 互操作

JavaScript 是一种脚本语言,与 CPU 实际执行的机器码相距甚远。不过,JavaScript 也有一种执行二进制机器码——或者接近机器码的东西——的方式:WebAssembly。WebAssembly 简称 Wasm,是一种底层、可移植的二进制格式,可以在浏览器中以接近原生的速度运行。

Wasm 是 C、C++ 和 Rust 等语言的编译目标,让 Google Earth、Photoshop 这类高性能应用能够直接在浏览器中运行。原文认为,严格的沙箱隔离也赋予它较高的安全性,使其适合金融、医疗等敏感应用。随着 Deno 2.1 提供一等 Wasm 支持,使用 Wasm 模块变得更加简单。

下面将构建一个简单的 Wasm 模块,并通过它从 JavaScript 调用 Rust。

构建 Wasm 模块

先构建一个简单的 Wasm 模块,再将它导入 Deno。

从一个名为 add 的小函数开始,使用 WebAssembly 文本格式编写。新建 add.wat 文件,并添加以下内容:

(module
  (func (export "add") (param $a i32) (param $b i32) (result i32)
    local.get $a
    local.get $b
    i32.add
  )
)

使用 wat2wasm 将它编译成 add.wasm:

wat2wasm add.wat

可以使用 Wasm Code Explorer 查看生成的 Wasm 二进制文件:

Add function visualized

现在,将 add.wasm 导入 Deno:

import { add } from "./add.wasm";

console.log(add(1, 2));

原文给出的输出如下:

> deno run main.ts
3

Deno 导入 Wasm 时能够理解模块的导出,并对它们进行类型检查。有关导入 Wasm 的更多信息,请参阅文档。

这个例子很简单,但在大多数生产场景中,人们会把 Rust、C++ 或 Go 编译为 Wasm,而不是手写 wat。

通过 Wasm 从 JavaScript 调用 Rust

接下来,使用 wasmbuild 将 Rust 函数导入 JavaScript。这个命令行工具通过 wasm-bindgen 生成胶水代码,让 JavaScript 可以调用 Rust crate。

首先确认已经安装 Deno 和 Rust(可用 deno -v、rustup -v 和 cargo -v 检查)。在一个新目录中创建 deno.json:

{
  "tasks": {
    "wasmbuild": "deno run -A jsr:@deno/wasmbuild@0.19.0"
  }
}

带上 new 参数运行任务:

$ deno task wasmbuild new
Task wasmbuild deno run -A jsr:@deno/wasmbuild@0.19.0 "new"
Creating rs_lib...
To get started run:
deno task wasmbuild
deno run mod.js

该命令会在 rs_lib 中生成 Rust crate 的脚手架,其中包含示例函数和测试:

// rs_lib/src/lib.rs
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
  a + b
}

#[wasm_bindgen]
pub struct Greeter {
  name: String,
}

#[wasm_bindgen]
impl Greeter {
  #[wasm_bindgen(constructor)]
  pub fn new(name: String) -> Self {
    Self { name }
  }

  pub fn greet(&self) -> String {
    format!("Hello {}!", self.name)
  }
}

#[cfg(test)]
mod tests {
  use super::*;

  #[test]
  fn it_adds() {
    let result = add(1, 2);
    assert_eq!(result, 3);
  }

  #[test]
  fn it_greets() {
    let greeter = Greeter::new("world".into());
    assert_eq!(greeter.greet(), "Hello world!");
  }
}

这个文件定义了 add 函数:接收两个有符号整数,返回一个有符号整数;还定义了带有两个方法的 Greeter 结构体。通过 #[wasm_bindgen] 属性,它们被导出供 JavaScript 使用。

你可以在这里编写自己的 Rust 代码;本例继续使用生成的代码。

接着运行 wasmbuild 任务来构建项目:

$ deno task wasmbuild

这会生成以下文件:

  • lib/rs_lib.internal.js
  • lib/rs_lib.js
  • lib/rs_lib.d.ts
  • lib/rs_lib.wasm
  • mod.js

我们可以通过 Wasm Code Explorer 查看生成的 Wasm 二进制文件 lib/rs_lib.wasm:

Rust function visualized

现在导入模块。上面列出的最后一个文件 mod.js,实际上已经包含了从 JavaScript 导入 Rust 函数的示例:

import { add, Greeter } from "./lib/rs_lib.js";

// adds
console.log(add(1, 1));

// greets
const greeter = new Greeter("world");
console.log(greeter.greet());

这段代码导入并执行 add 和 Greeter。它们原本在 Rust 中定义,经编译和胶水代码封装后可从 JavaScript 调用。原文使用 deno mod.js 尝试运行:

$ deno mod.js
2
Hello world!

原文示例成功运行了!

想进一步了解 Rust 与 JavaScript 的结合?请阅读用 Rust 构建自己的 JavaScript 运行时。

接下来呢?

希望这份循序渐进的 WebAssembly 入门,不仅展示了怎样在 JavaScript 和浏览器中使用它,也能给你带来一些潜在用途的灵感。

在 Deno 2.1 中,导入 Wasm 模块与导入普通 JavaScript 模块一样容易。对于 Rust,作者计划通过简化 Wasm 编译步骤,并仅暴露更高层的 JavaScript API,进一步改善 wasmbuild 的 Rust 到 JavaScript 导入体验。

最后,下面这些资源展示了 Wasm 的能力,以及如何用它改进项目:

Deno 2.1 已发布

以及更多功能!


来源与版权

原文作者:Andy Jiang、David Sherret。来源:Deno Blog,2025-01-28。依用户已声明的转载授权译载;作者署名与原文链接保留。原页面没有单独列出本文的许可文本,不据此推定为公共领域。 阅读原文。

Deno 运行命令文档

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容