通过从 JavaScript 调用 Rust 来构建和增强库

探索如何使用 WebAssembly (Wasm) 将 Rust 嵌入到 JavaScript 中。
171 位读者喜欢这篇文章。
JavaScript in Vim

Alex Sanchez。CC BY-SA 4.0。

为什么你应该在 WebAssembly 中使用 Rust?中,我探讨了你可能想要编写 WebAssembly (Wasm) 的原因,以及为什么你可能选择 Rust 作为执行此操作的语言。现在我将分享它的外观,方法是探索将 Rust 嵌入到 JavaScript 中的方法。

这是 Rust 与 Go、C# 和其他具有大型运行时且可以编译为 Wasm 的语言的区别之处。Rust 具有最小的运行时(基本上只是一个分配器),这使得从 JavaScript 库中使用 Rust 变得容易。C 和 C++ 也有类似的情况,但 Rust 的独特之处在于它的工具,我们现在将对其进行了解。

基础知识

如果你以前从未使用过 Rust,你首先需要进行设置。这非常容易。首先下载 Rustup,这是一种控制 Rust 版本和用于交叉编译的不同工具链的方法。这将使你可以访问 Cargo,它是 Rust 构建工具和包管理器。

现在我们需要做一个决定。我们可以轻松地编写通过 WebAssembly 在浏览器中运行的 Rust 代码,但是如果我们想做一些除了让人们的 CPU 风扇旋转之外的事情,我们可能在某个时候想要与文档对象模型 (DOM) 交互或使用一些 JavaScript API。换句话说:我们需要 JavaScript 互操作(也称为 JavaScript 互操作性 API)。

问题和解决方案

WebAssembly 是一种极其简单的机器语言。如果我们想要能够与 JavaScript 通信,Wasm 只提供了四种数据类型来执行此操作:32 位和 64 位浮点数和整数。Wasm 没有字符串、数组、对象或任何其他丰富数据类型的概念。基本上,我们只能在 Rust 和 JavaScript 之间传递指针。不用说,这远非理想。

好消息是,有两个库可以促进基于 Rust 的 Wasm 和 JavaScript 之间的通信:wasm-bindgenstdweb。然而,坏消息是这两个库不幸地彼此不兼容。wasm-bindgenstdweb 更底层,并试图提供对 JavaScript 和 Rust 如何交互的完全控制。事实上,甚至有关于使用 wasm-bindgen 重写 stdweb的讨论,这将消除不兼容性问题。

因为 wasm-bindgen 是更轻量级的选择(并且是官方 Rust WebAssembly 工作组 官方工作的选项),我们将重点关注它。

wasm-bindgen 和 wasm-pack

我们将创建一个函数,该函数从 JavaScript 中获取一个字符串,将其转换为大写并在其前面加上 "HELLO, ",然后将其返回给 JavaScript。我们将这个函数称为 excited_greeting

首先,让我们创建我们的 Rust 库,它将包含这个出色的函数

$ cargo new my-wasm-library --lib
$ cd my-wasm-library

现在我们要用我们令人兴奋的逻辑替换 src/lib.rs 的内容。我认为最好写出代码而不是复制/粘贴。

// Include the `wasm_bindgen` attribute into the current namespace. 
use wasm_bindgen::prelude::wasm_bindgen;

// This attribute makes calling Rust from JavaScript possible.
// It generates code that can convert the basic types wasm understands 
// (integers and floats) into more complex types like strings and 
// vice versa. If you're interested in how this works, check this out:
// https://blog.ryanlevick.com/posts/wasm-bindgen-interop/
#[wasm_bindgen]
// This is pretty plain Rust code. If you've written Rust before this
// should look extremely familiar. If not, why wait?! Check this out:
// https://doc.rust-lang.net.cn/book/
pub fn excited_greeting(original: &str) -> String {
  format!("HELLO, {}", original.to_uppercase())
}

其次,我们必须对我们的 Cargo.toml 配置文件进行两项更改

  • 添加 wasm_bindgen 作为依赖项。
  • 将库二进制文件的类型配置为 cdylib 或动态系统库。在这种情况下,我们的系统是 wasm,设置此选项是我们生成 .wasm 二进制文件的方式。
[package]
name = "my-wasm-library"
version = "0.1.0"
authors = ["$YOUR_INFO"]
edition = "2018"

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
wasm-bindgen = "0.2.33"

现在让我们构建!如果我们只使用 cargo build,我们将获得一个 .wasm 二进制文件,但是为了方便从 JavaScript 调用我们的 Rust 代码,我们希望有一些 JavaScript 代码将丰富的 JavaScript 类型(如字符串和对象)转换为指针,并将这些指针代表我们传递给 Wasm 模块。手动执行此操作既繁琐又容易出错。

幸运的是,除了作为一个库之外,wasm-bindgen 还具有为我们创建这种“粘合”JavaScript 的能力。这意味着在我们的代码中,我们可以使用普通的 JavaScript 类型与我们的 Wasm 模块交互,并且从 wasm-bindgen 生成的代码将完成将这些丰富的类型转换为 Wasm 实际理解的指针类型的繁琐工作。

我们可以使用出色的 wasm-pack 来构建我们的 Wasm 二进制文件,调用 wasm-bindgen CLI 工具,并将我们所有的 JavaScript(以及任何可选生成的 TypeScript 类型)打包到一个漂亮整洁的包中。现在让我们这样做!

首先我们需要安装 wasm-pack

$ cargo install wasm-pack

默认情况下,wasm-bindgen 生成 ES6 模块。我们将从一个简单的 script 标签中使用我们的代码,所以我们只希望它生成一个普通的 JavaScript 对象,该对象使我们可以访问我们的 Wasm 函数。为此,我们将向其传递 --target no-modules 选项。

$ wasm-pack build --target no-modules

我们现在在我们的项目中有一个 pkg 目录。如果我们查看其内容,我们将看到以下内容

  • package.json:如果我们想将其打包为 NPM 模块,则很有用
  • my_wasm_library_bg.wasm:我们实际的 Wasm 代码
  • my_wasm_library.js:JavaScript “粘合”代码
  • 一些 TypeScript 定义文件

现在我们可以创建一个 index.html 文件,它将使用我们的 JavaScript 和 Wasm

<html>
<head>
  <meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
</head>
<body>
  <!-- Include our glue code --> 
  <script src='./pkg/my_wasm_library.js'></script>
  <!-- Include our glue code --> 
  <script>
    window.addEventListener('load', async () => {
      // Load the wasm file
      await wasm_bindgen('./pkg/my_wasm_library_bg.wasm');
      // Once it's loaded the `wasm_bindgen` object is populated 
      // with the functions defined in our Rust code
      const greeting = wasm_bindgen.excited_greeting("Ryan")
      console.log(greeting)
    });
  </script>
</body>
</html>

你可能会想在浏览器中打开 HTML 文件,但不幸的是,这是不可能的。出于安全原因,Wasm 文件必须从与 HTML 文件相同的域提供。你需要一个 HTTP 服务器。如果你有喜欢的静态 HTTP 服务器可以从你的文件系统提供文件,请随时使用它。我喜欢使用 basic-http-server,你可以像这样安装和运行它

$ cargo install basic-http-server
$ basic-http-server

现在通过访问 http://localhost:4000/index.html 在 Web 服务器中打开 index.html 文件,并检查你的 JavaScript 控制台。你应该在那里看到一个非常令人兴奋的问候!

如果你有任何问题,请 告诉我。下次,我们将了解如何从我们的 Rust 代码中使用各种浏览器和 JavaScript API。

标签
User profile image.
Ryan 是一位在柏林微软担任开发者布道师的程序员。在他的日常工作中,他可以使用许多不同的编程语言进行编程,但他最喜欢使用 Rust。除了编写 Rust 之外,Ryan 还喜欢探索柏林和其他城市的空间、烹饪/吃饭和肆无忌惮地跳舞。

评论已关闭。

© . All rights reserved.