Webassembly - run native code on browser using Rust
跑在瀏覽器上的組合語言?在 ban 了 flash 之後,大家在尋找是否有其他可以在瀏覽器上執行原生程式的方案,又或者只是不想寫 Javascript…Webassembly 這個標準被提出了(以下簡稱 wasm),可以是 C/C++ 這種靜態語言的編譯目標,也就是我們可以把現有的 C/C++ 專案編譯成 wasm,有人就嘗試把 ffmpeg 編譯到瀏覽器上執行,後來 golang 以及 rust 也可以編譯成 wasm ,今天來用 rust 試試看 wasm 吧
基本上這篇我是看著 https://rustwasm.github.io/book/ 做的
Rust
我選擇 rust 純粹只是因為我沒用過,聽說對於 concurrent 有一套很獨特的概念,想嘗試看看而已,首先來把 rust (rustup
) 安裝起來:https://www.rust-lang.org/tools/install
我對 rustup
的理解: rust 官方的 rust 版本管理器
安裝完成之後,安裝現在的 rust 穩定版
rustup toolchain install stable
rustup default stable
準備 rust wasm 開發環境以及工具
基本上現在使用 wasm 的方法是比較像是寫底層原生程式給 js 呼叫,必須先用 js 載入 wasm,接著用 js 呼叫 wasm 的 exports
但是要怎麼讓編譯器知道哪些東西要給 js 呼叫?js 跟 rust 互傳變數/資料的時候如何各自表述?甚至載入 wasm 可能各家瀏覽器的 API 都不盡相同…幸好 rust wasm 已經有許多現成的工具包幫我們解決這些問題,所以接下來要先來把開發環境準備好
wasm-pack
首先 wasm-pack 是 rust 跟 js 橋樑的核心,它的 macro 讓指定的 rust 函式以及資料結構提供給 js 端使用,當然也就處理了兩邊的資料轉換,最後它還幫忙生成 js 讓 webapp 端可以直接 import 使用
cargo install wasm-pack
cargo
是 rust 的npm
/gem
/bundle
,我的理解是這樣
cargo-generate
& wasm-pack-template
同時也有人弄了新手模板 wasm-pack-template,但是為了使用他我們要裝 cargo-generate
,於是乎:
cargo install cargo-generate
create-wasm-app
rust 的部份準備好了,但是我們要怎麼弄個能夠載入 wasm 的 webapp?還有個 wasm webapp 的新手模板 create-wasm-app,這個幫你把 webpack 設定值都準備好,而且就是拿來跟 wasm-pack-template
搭配使用的,但是也意味著我們需要 nodejs,而且我用 nodejs 11.6.0
才能用 npm 從模板建立專案
node -v # => v11.6.0
npm install -g create-wasm-app
終於可以來建立專案囉
這邊建立一個叫做 wasm-glife
的 wasm-pack-template 專案:
cargo generate --git https://github.com/rustwasm/wasm-pack-template -n wasm-glife
cd wasm-glife
還沒完,還要把 webapp 建立起來,弄個 www
資料夾來放 webapp:
mkdir www
cd www
npm init wasm-app
npm install
看來產生不少東西,看一下哪些檔案值得我們關心
tree .
.
├── Cargo.toml <= 給 rust 的 package.json
├── LICENSE_APACHE
├── LICENSE_MIT
├── README.md
├── src
│ ├── lib.rs <= 將會是接下來的重頭戲,我們會在這邊定義給 js 呼叫的東西
│ └── utils.rs
├── tests
│ └── web.rs
└── www <= 大家熟悉的 webpack 專案
├── bootstrap.js
├── index.html
├── index.js <= 將會在這邊呼叫 rust wasm
├── LICENSE-APACHE
├── LICENSE-MIT
├── package.json
├── package-lock.json
├── README.md
└── webpack.config.js
Cargo.toml
[dependencies]
cfg-if = "0.1.2"
wasm-bindgen = "0.2"
這邊最重要的就是 wasm-bindgen
這個套件,待會會看到相關的 macro
src/lib.rs
wasm-pack-template 產生的 src/lib.rs
一開始引入一些東西,configure 一些東西,先不管他,下面這兩個已經完整示範了要怎麼在 rust 使用 js function 以及把 rust function 提供給 js 使用
#[wasm_bindgen]
extern {
fn alert(s: &str);
}
#[wasm_bindgen]
pub fn greet() {
alert("Hello, wasm-glife!");
}
#[wasm_bindgen]
macro 加上extern
表示從 js 拿哪些 function 使用,這邊把我們常用的alert
拿進來#[wasm_bindgen]
macro 加上pub fn ...
讓 js 可以呼叫哪些 rust function,這邊把greet
拿出去給 js 呼叫
www/index.js
import * as wasm from "hello-wasm-pack";
wasm.greet();
js 這邊就把 wasm 當成一般的 ES module 引入,接著呼叫 greet()
但是我們現在還沒編譯 rust 到 wasm,這時直接去跑 webpack 是編譯不過的,畢竟 hello-wasm-pack
還沒編譯出來
編譯 rust 到 wasm
rustup run stable wasm-pack build -d www/wasm
rustup run stable ...
的部份,我想是類似bundle exec ...
,去執行對應 rust 版本的執行檔wasm-pack build
就能編譯這個 rust 專案成一個 node module-d www/wasm
則是讓編譯出來的 node module 放在www/wasm
按下 enter 之後就會開始下載 dependency,編譯,第一次編譯約莫幾分鐘
線上書的做法是產生一個真的 local npm module…但是我覺得沒必要這麼麻煩,直接把東西丟在 webpack 專案裡頭就能用了
串到 webapp
wasm-pack
成功之後會產生蠻多東西的,不過我們 focus 在產生的 node module
tree .
...
└── www
├── bootstrap.js
├── index.html
├── index.js
├── LICENSE-APACHE
├── LICENSE-MIT
├── package.json
├── package-lock.json
├── README.md
├── wasm <= wasm-pack 編譯出來的 node module
│ ├── package.json <= "module": "wasm_glife.js"
│ ├── README.md
│ ├── wasm_glife_bg.d.ts
│ ├── wasm_glife_bg.wasm <= 就是我,wasm
│ ├── wasm_glife.d.ts
│ └── wasm_glife.js <= wasm-pack 會把必要的橋接產生在這
└── webpack.config.js
因為產生的是 node module,透過 webpack 可以直接 import 使用,我們只要把 www/index.js
import 改成
import * as wasm from "./wasm";
接著把 webpack 跑起來
npm run start
用支援 wasm 的瀏覽器打開 http://localhost:8080/ 應該可以看到:
在打開瀏覽器開發工具切到 Network tab,確實可以看到 wasm 被載入了:
wasm 是 binary 格式的,需要用工具才能轉回可讀的文字:
最後偷瞄一下 webpack 怎麼幫我們載入 wasm 的 (bootstrap.js
):
實做一些東西來玩玩:Conway’s game of life
因為我看的這本線上書就是要來做 Conway’s Game of Life,基本上就是在一個棋盤上,每個格子可以是死亡或是存活兩種狀態,並且會根據周圍 8 格鄰居格子的狀態決定下回合的狀態
但是我對 rust 還沒這麼熟,所以就邊看邊抄,邊抄邊學,嘗試寫成自己的版本:https://github.com/pastleo/rs-wasm-glife/
- src/universe.rs 實做核心資料結構以及演算法
- src/main.rs 讓我可以編譯成傳統 binary 執行檔在 console 上執行並印出棋盤
- src/lib.rs 包裝成
Game
class 給 js 使用 - www/index.html 以及 www/index.js 實做使用者界面
game.isChanged(i)
決定是否要 toggle css class 避免不必要的 DOM 改動- 使用者可以按格子來更改狀態
我也把東西放到網站上大家可以上去玩玩:https://static.pastleo.me/rs-wasm-glife/
蠻好玩的,我可以玩很久
心得
其實 conway’s game of life 用 rust -> wasm 很可能沒有比純 js 實做來的快,說不定光是在 rust 跟 js 之間的資料轉換的效能犧牲就已經不值得了,現在 wasm 是以 MVP 的概念先在各家瀏覽器支援,我個人比較期待的是還沒定案的這兩個功能:
這幾天就花點時間試試看 wasm,體驗一下在自己機器開發/執行的感覺(話說在 android chrome 跟 ios safari 都跑得起來)順便學一下 rust