翻译正在进行中。

如果你写了一些 Rust 代码,你可以把它编译成 WebAssembly!这份教程将带你编译 Rust 项目为 wasm 并在一个现存的 web 应用中使用它。

Rust 和 WebAssembly 用例

Rust 和 WebAssembly 有两大主要用例:

  • 构建完整应用 —— 整个 Web 应用都基于 Rust 开发!
  • 构建应用的组成部分 —— 在现存的 JavaScript 前端中使用 Rust。

目前,Rust 团队正专注于第二种用例,因此我们也将着重介绍它。对于第一种用例,可以参阅 yew 这类项目。

在本教程中,我们将使用 Rust 的 npm 包构建工具 wasm-pack 来构建一个 npm 包。这个包只包含 WebAssembly 和 JavaScript 代码,以便包的用户无需安装 Rust 就能使用。他们甚至不需要知道这里包含 WebAssembly!

安装 Rust 环境

让我们看看安装 Rust 环境的所有必要步骤。

安装Rust

前往 Install Rust 页面并跟随指示安装 Rust。这里会安装一个名为 “rustup” 的工具,这个工具能让你管理多个不同版本的 Rust。默认情况下,它会安装用于惯常Rust开发的 stable 版本 Rust Release。Rustup 会安装 Rust 的编译器 rustc、Rust 的包管理工具 cargo、Rust 的标准库 rust-std 以及一些有用的文档 rust-docs

Note: 需要注意,在安装完成后,你需要把 cargo 的 bin 目录添加到你系统的 PATH 。一般来说它会自动添加,但需要你重启终端后才会生效。 

wasm-pack

要构建我们的包,我们需要一个额外工具 wasm-pack。它会帮助我们把我们的代码编译成 WebAssembly 并制造出正确的 npm 包。使用下面的命令可以下载并安装它:

$ cargo install wasm-pack

安装 Node.js 并获取 npm 账户

We'll be building an npm package in this tutorial, and so you'll need to have Node.js and npm installed. Additionally, we'll be publishing our package to npm, and so you'll need an npm account as well. They're free! You don't technically have to publish the package, but using it is much easier that way, and so we'll be assuming that you do in this tutorial.

To get Node.js and npm, go to the Get npm! page and follow the instructions. When it comes to picking a version, choose any one you'd like; this tutorial isn't version-specific.

To get an npm account, go to the npm signup page and fill out the form.

Next, at the command line run npm adduser:

> npm adduser
Username: yournpmusername
Password:
Email: (this IS public) you@example.com

You'll need to fill out your own username, password, and email. If it worked, you'll see this printed:

Logged in as yournpmusername on https://registry.npmjs.org/.

If something didn't work, contact npm to get it sorted.

构建我们的 WebAssembly npm 包

万事俱备,来创建一个新的 Rust 包吧。打开你用来存放你私人项目的目录,做这些事:

$ cargo new --lib hello-wasm
     Created library `hello-wasm` project

这里会在名为 hello-wasm 的子目录里创建一个新的库,里面有下一步之前你所需要的一切:

+-- Cargo.toml
+-- src
    +-- lib.rs

首先,我们有一个 Cargo.toml 文件,这是我们配置构建的方式。如果你用过 Bundler 的 Gemfile 或者 npm 的 package.json,你应该会感到很熟悉。Cargo 的用法和它们类似。

接下来,Cargo 在 src/lib.rs 生成了一些 Rust 代码:

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}

我们完全不需要使用这些测试代码,所以继续吧,我们删掉它。

来写点 Rust 代码吧!

让我们在 src/lib.rs 写一些代码替换掉原来的:

extern crate wasm_bindgen;

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
extern {
    pub fn alert(s: &str);
}

#[wasm_bindgen]
pub fn greet(name: &str) {
    alert(&format!("Hello, {}!", name));
}

This is the contents of our Rust project. It has three main parts, let's talk about them in turn. We'll give a high-level explanation here, and gloss over some details; to learn more about Rust, please check the free online book The Rust Programming Language.

使用 wasm-bindgen 在 Rust 与 JavaScript 之间通信

第一部分看起来像这样:

extern crate wasm_bindgen;

use wasm_bindgen::prelude::*;

第一行就像在说 “哇 Rust,我们在用一个叫做 wasm_bindgen 的库”。在 Rust 当中,库被称为 “crates”,因为我们使用的是一个外部库,所以有  "extern"。

明白了吗? Cargo ships crates.

The third line contains a use command, which imports code from a library into your code. In this case, we're importing everything in the wasm_bindgen::prelude module. We'll use these features in the next section.

Before we move on to the next section, we should talk a bit more about wasm-bindgen.

wasm-pack uses wasm-bindgen, another tool, to provide a bridge between the types of JavaScript and Rust. It allows JavaScript to call a Rust API with a string, or a Rust function to catch a JavaScript exception.

We'll be using wasm-bindgen's functionality in our package. In fact, that's the next section!

在 JavaScript 中调用来自 Rust 的外部函数

接下来的部分看起来像这样:

#[wasm_bindgen]
extern {
    pub fn alert(s: &str);
}

The bit inside the #[] is called an "attribute", and it modifies the next statement somehow. In this case, that statement is an extern, which tells Rust that we want to call some externally defined functions. The attribute says "wasm-bindgen knows how to find these functions".

The third line is a function signature, written in Rust. It says "the alert function takes one argument, a string named s."

You may have a suspicion as to what this function is, and your suspicion may be right: this is the alert function provided by JavaScript! We'll be calling this function in the next section.

Whenever you want to call new JavaScript functions, you can write them in here, and wasm-bindgen will take care of setting everything up for you. Not everything is supported yet, but we're working on it! Please file bugs if something is missing.

编写能够在JavaScript中调用的 Rust 函数

最后一部分是这样的:

#[wasm_bindgen]
pub fn greet(name: &str) {
    alert(&format!("Hello, {}!", name));
}

我们又看到了 #[wasm_bindgen] 属性。在这里,它并非定义一个 extern 块,而是 fn,这代表我们希望能够在 JavaScript 中使用这个 Rust 函数。 这和 extern 正相反:我们并非引入函数,而是要把函数给外部世界使用。

这个函数的名字是 greet,它需要一个参数,一个字符串 (写作 &str)。它调用了我们前面在 extern 块中引入的 alert 函数。它传递了一个让我们串联字符串的 format! 宏的调用。

format! 在这里有两个参数,一个格式化字符串和一个要填入的变量。格式化字符串是 "Hello, {}!" 部分。它可以包含一个或多个 {},变量将会被填入其中。传递的变量是 name,也就是这个函数的参数。所以当我们调用 greet("Steve")时我们就能看到 "Hello, Steve!"。

这个传递到了 alert(),所以当我们调用这个函数时,我们应该能看到他谈弹出了一个带有 "Hello, Steve!" 的消息框。

我们的库写完了,是时候构建它了。

把我们的代码编译到 WebAssembly

To compile our code correctly, we first need to configure it with Cargo.toml. Open this file up, and change it's contents to look like this:

[package]
name = "hello-wasm"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]
description = "A sample project with wasm-pack"
license = "MIT/Apache-2.0"
repository = "https://github.com/yourgithubusername/hello-wasm"

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

[dependencies]
wasm-bindgen = "0.2"

You'll need to fill in your own repository, and Cargo should have filled in authors based on the info git uses.

The big part to add is the stuff at the bottom. The first part — [lib] — tells Rust to build a cdylib version of our package; we won't get into what that means in this tutorial. For more, consult the Cargo and Rust Linkage documentation.

The second section is the [dependencies] section. Here's where we tell Cargo what version of wasm-bindgen we want to depend on; in this case, that's any 0.2.z version (but not 0.3.0 or above).

构建包

Now that we've got everything set up, let's build! Type this into your terminal:

$ wasm-pack build --scope mynpmusername

This will do a number of things (and they take a lot of time, especially the first time you run wasm-pack). To learn about them in detail, check out this blog post on Mozilla Hacks. In short, wasm-pack build will:

  1. Compile your Rust code to WebAssembly.
  2. Run wasm-bindgen on that WebAssembly, generating a JavaScript file that wraps up that WebAssembly file into a module npm can understand.
  3. Create a pkg directory and move that JavaScript file and your WebAssembly code into it.
  4. Read your Cargo.toml and produce an equivalent package.json.
  5. Copy your README.md (if you have one) into the package.

The end result? You have an npm package inside of the pkg directory.

对代码体积的一些说明

If you check out the generated WebAssembly code size, it may be a few hundred kilobytes. We haven't instructed Rust to optimize for size at all, and doing so cuts down on the size a lot. This is off-topic for this tutorial, but if you'd like to learn more, check out the Rust WebAssembly Working Group's documentation on Shrinking .wasm Size.

把我们的包发布到 npm

把我们的新包发布到 npm registry:

$ cd pkg
$ npm publish --access=public

We now have an npm package, written in Rust, but compiled to WebAssembly. It's ready for use from JavaScript, and doesn't require the user to have Rust installed; the code included was the WebAssembly code, not the Rust source!

在网站上使用我们的包

Let's build a website that uses our new package! Many people use npm packages through various bundler tools, and we'll be using one of them, webpack, in this tutorial. It's only slightly more complex, and shows off a more realistic use-case.

Let's move back out of the pkg directory, and make a new directory, site, to try this out in:

$ cd ../..
$ mkdir site
$ cd site

Create a new file, package.json, and put the following code in it:

{
  "scripts": {
    "serve": "webpack-dev-server"
  },
  "dependencies": {
    "@mynpmusername/hello-wasm": "^0.1.0"
  },
  "devDependencies": {
    "webpack": "^4.25.1",
    "webpack-cli": "^3.1.2",
    "webpack-dev-server": "^3.1.10"
  }
}

Note that you'll need to fill in your own username, after the @, in the dependencies section.

Next, we need to configure Webpack. Create webpack.config.js and put the following in it:

const path = require('path');
module.exports = {
  entry: "./index.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "index.js",
  },
  mode: "development"
};

Now we need an HTML file; create an index.html and give it the following contents:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>hello-wasm example</title>
  </head>
  <body>
    <script src="./index.js"></script>
  </body>
</html>

Finally, create the index.js referenced in the HTML file and give it these contents:

const js = import("./node_modules/@yournpmusername/hello-wasm/hello_wasm.js");
js.then(js => {
  js.greet("WebAssembly");
});

Note that you need to fill in your npm username again.

This imports our module from the node_modules folder. This isn't considered a best practice, but this is a demo, so we'll work with it for now. Once it's loaded, it calls the greet function from that module, passing "WebAssembly" as a string. Note how there's nothing special here, yet we're calling into Rust code! As far as the JavaScript code can tell, this is just a normal module.

We're done making files! Let's give this a shot:

$ npm install
$ npm run serve

This will start up a web server. Load up http://localhost:8080 and you should see an alert box come on the screen, with Hello, WebAssembly! in it! We've successfully called from JavaScript into Rust, and from Rust into JavaScript.

结论

本教程到此结束。希望你觉得它有用。

在这个领域,有很多工作正在推进当中。如果你希望它变得更好,可以参阅  Rust Webassembly 工作组

文档标签和贡献者

标签: 
此页面的贡献者: wymm0008
最后编辑者: wymm0008,