編譯 Rust 爲 WebAssembly

如果你寫了一些 Rust 代碼,你可以把它編譯成 WebAssembly!這份教程將帶你編譯 Rust 項目爲 wasm 並在一個現存的 web 應用中使用它。

Rust 和 WebAssembly 有兩大主要用例:

目前,Rust 團隊正專注於第二種用例,因此我們也將着重介紹它。對於第一種用例,可以參閱 [yew](https://github.com/DenisKolodin/yew) 這類項目。

在本教程中,我們將使用 Rust 的 npm 包構建工具 wasm-pack 來構建一個 npm 包。這個包只包含 WebAssembly 和 JavaScript 代碼,以便包的用戶無需安裝 Rust 就能使用。他們甚至不需要知道這裏包含 WebAssembly!

讓我們看看安裝 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。它會幫助我們把我們的代碼編譯成 WebAssembly 並製造出正確的 npm 包。使用下面的命令可以下載並安裝它:

$ cargo install wasm-pack

在這個例子中我們將會構建一個 npm 包,因此你需要確保安裝 Node.js 和 npm 已經安裝。另外,我們將會把包發佈到 npm 上,因此你還需要一個 npm 賬號。它們是免費的。發佈這個包並不是必須的,但是發佈它非常簡單,因此在本例中我們默認你會發布這個包。

Get npm! 頁面按照說明下載並安裝 Node.js 和 npm。在選擇版本時,選擇一個你喜歡的版本;本例不限定特定版本。

npm signup page 註冊 npm 賬戶,並填寫表格。

接下來,在命令行中運行 npm adduser:

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

你需要完善你的用戶名,密碼和郵箱。如果成功了,你將會看到:

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

如果並未正常運行,請聯繫 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);
    }
}

我們完全不需要使用這些測試代碼,所以繼續吧,我們刪掉它。

讓我們在 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));
}

這就是我們這個 Rust 項目的內容。它有三個主要部分,讓我們按順序來講。這裏將會給出一個缺少部分細節的高級說明;如果想要了解更多 Rust 知識,請查看在線書籍 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.

第三行包括了一個將庫中的代碼引入到你的代碼中的使用命令。在這個情況下,將會引入 wasm_bindgen::prelude 的全部模塊。我們將在下一節中使用這些內容。

在我們開始下一節之前,我們將講一講 wasm-bindgen.

wasm-pack 使用 wasm-bindgen,其它工具,去提供一個連接 JavaScript 和 Rust 的橋。它允許 JavaScript 使用 string 調用 Rust API,或者調用一個 Rust function 去捕獲 JavaScript 異常。

我們將在我們的包中使用 wasm-bindgen 的功能。事實上,這是下一節的內容!

在 Rust 中調用來自 JavaScript  的外部函數

接下來的部分看起來像這樣:

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

#[] 中的內容叫做 "屬性",並以某種方式改變下面的語句。在這種情況下,下面的語句是一個 extern,它將告訴 Rust that 我們想調用一些外部定義的函數。這個屬性告訴我們 "wasm-bindgen 知道如何找到這些函數"。

第三行是用 Rust 寫的函數簽名。它告訴我們 "alert 函數接受一個叫做 s 的字符串作爲參數。"

你可能會疑惑這個函數是什麼,你的疑惑可能是正確的:這是 the alert function provided by JavaScript!我們將在下一節中調用這個函數。

當你想調用新的 JavaScript 函數時,你可以在這裏寫他們,wasm-bindgen 將負責爲您設置一切。並非一切都得到支持,但我們正在努力!如果缺少某些內容,請 file bugs

編寫能夠在 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!" 的消息框。

我們的庫寫完了,是時候構建它了。

爲了能夠正確的編譯我們的代碼,首先我們需要配置 Cargo.toml。打開這個文件,將內容改爲如下所示:

[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"

你需要改爲自己的倉庫,同時 Cargo 需要通過 git 來完善 authors 部分。

最重要的是添加底下的部分。第一個部分 — [lib] — 告訴 Rust 爲我們的包建立一個 cdylib 版本;在本教程中我們不會講解它的含義。有關更多信息,請參閱 CargoRust Linkage 文檔。

第二個部分是 [dependencies] 部分。在這裏我們告訴 Cargo 我們需要依賴哪個版本的 wasm-bindgen ;在這個例子中,它是 0.2.z 版本的 (不是 0.3.0 或者其他版本)。

現在我們已經完成了所有配置項,開始構建吧!在命令行輸入以下命令:

$ wasm-pack build --scope mynpmusername

這個命令將做一系列事情 (這會花一些時間,特別是當你第一次運行 wasm-pack)。想了解詳細情況,查看這篇在 Mozilla Hacks 上的文章。簡單來說,wasm-pack build 將做以下幾件事:

  1. 將你的 Rust 代碼編譯成 WebAssembly。
  2. 在編譯好的 WebAssembly 代碼基礎上運行 wasm-bindgen,生成一個 JavaScript 文件將 WebAssembly 文件包裝成一個模塊以便 npm 能夠識別它。
  3. 創建一個 pkg 文件夾並將 JavaScript 文件和生成的 WebAssembly 代碼移到其中。
  4. 讀取你的 Cargo.toml 並生成相應的 package.json。
  5. 複製你的 README.md (如果有的話) 到文件夾中。

最後的結果?你在 pkg 文件夾下有了一個 npm 包。

對代碼體積的一些說明

如果你檢查生成的 WebAssembly 文件體積,它可能有幾百 kB。我們沒有讓 Rust 去壓縮生成的代碼,從而大大減少生成包的體積。這和本次教程主題無關,但如果你想了解更多,查看 Rust WebAssembly 工作組文檔上關於 減少 .wasm 體積 的說明。

把我們的新包發佈到 npm registry:

$ cd pkg
$ npm publish --access=public

我們現在有了一個 npm 包,使用 Rust 編寫,但已經被編譯爲 WebAssembly 了。現在這個包已經可以被 JavaScript 使用了,而且使用它完全不需要用戶安裝 Rust ;包中的代碼是 WebAssembly 代碼,而不是 Rust 源碼!

讓我們建立一個使用我們包的網站! 人們通過各種打包工具使用 npm 包,在本教程中,我們將使用 webpack。 它比其他某些打包工具稍微複雜一點,但展示了更實際的用法。

讓我們離開pkg目錄,並創建一個新目錄site,嘗試以下操作:

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

創建一個新文件 package.json,然後輸入如下代碼:

{
  "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"
  }
}

請注意,您需要在依賴項部分的 @ 之後填寫自己的用戶名。

接下來,我們需要配置 Webpack。 創建 webpack.config.js 並輸入:

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

現在我們需要一個 HTML 文件。 創建一個index.html並寫入如下內容:

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

最後,從 HTML 文件中引用index.js

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

請注意,您需要再次填寫您的 npm 用戶名。

這將從node_modules文件夾導入我們的模塊。這不是最佳做法,但這裏只做一個演示,因此暫時就這樣用。 加載後,它將從該模塊調用greet函數,並傳入字符串 “WebAssembly” 參數。注意這裏看上去沒有什麼特別的,但是我們正在調用 Rust 代碼! 就 JavaScript 代碼所知,這只是一個普通模塊。

我們已經完成了所有的文件! 讓我們試一下:

$ npm install
$ npm run serve

這將啓動一個 Web 服務器。 訪問 http://localhost:8080 ,您應該會在屏幕上看到一個警告框,其中包含 Hello, WebAssembly! !我們已經成功地從 JavaScript 調用了 Rust,並從 Rust 調用了 JavaScript。

本教程到此結束。希望你覺得它有用。

在這個領域,有很多工作正在推進當中。如果你希望它變得更好,可以參閱  Rust Webassembly 工作組

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://developer.mozilla.org/zh-CN/docs/WebAssembly/Rust_to_wasm