Tutorial - Rust and WebAssembly をやってみる
Rust と WebAssembly の入門として、以下のチュートリアルを行ってみる。
学習の際の試行錯誤を記入していく。
なお、日本語版もあるものの、翻訳は途中までで止まっている。
This tutorial is for anyone who already has basic Rust and JavaScript experience, and wants to learn how to use Rust, WebAssembly, and JavaScript together.
You should be comfortable reading and writing basic Rust, JavaScript, and HTML. You definitely do not need to be an expert.
うっ(JavaScript 大丈夫かな)
セットアップ
- Rustツールチェイン
-
wasm-pack
- リンク先のインストールスクリプトは現状まだ M1 Mac をサポートしていないらしい。
- かわりに
cargo install wasm-pack
で入れた。とりあえずすんなり入った
- cargo-generate
- npm (latest)
所感: cargo
やっぱ好き
Hello, World!
特に問題なし。 package.json
の中身が例と若干違った点が気になるぐらい。
{
"files": [
"wasm_game_of_life_bg.wasm",
"wasm_game_of_life.js",
"wasm_game_of_life_bg.js",
"wasm_game_of_life.d.ts"
],
}
いきなり様々なファイルが登場したが、とりあえず localhost:8080
で動かすところまではできた。
演習問題も自力でいけたのでここは良しとする。
疑問
-
www
ディレクトリ内にも.git/
があるためにサブもジュール扱いになるのだけど、これは実際にはどう管理するのだろう。 - 演習では
greet()
が1つの引数を取るように実装が変更されるのだが、ここで JS/TS 側でgreet()
関数を呼び出すときに引数を指定しなくてもコンパイルエラーにならなかった。Compile とログには出てくるが、 TS のような静的解析は行わないということだろうか。- ちなみにこのとき、開発者ツールのコンソールを開いてみると実行時にエラーが起きていることが分かる(ただ、エラーの内容はあんまり分かりやすいとは思えなかった。単に JS の経験不足かも)
Rules of Conway's Game of Life
コンウェイのライフゲーム。名前は聞いたことがある。
ここはこれから実装するゲームのルールを述べたもので、 WASM とは直接関係しない。
Wikipedia とかで調べてみると分かりやすい GIF が沢山あっておもしろい。
Implementing Conway's Game of Life
Interfacing Rust and JavaScript
この節はとても重要らしいので、ちゃんと要点をまとめておこう。
- JS の garbage-collected heap (GC heap) は WASM (Rust) の線形メモリ空間とは全く異なる場所にアロケートされている
- WASM は GC heap にアクセスできない(2018 年4月時点)
- 今後変わるかも
- 逆に、JS から線形メモリ空間へのアクセスは、スカラー値を要素にもつ
ArrayBuffer
としてであれば可能
- WASM は GC heap にアクセスできない(2018 年4月時点)
-
wasm_bindgen
はこの Rust と JS の垣根を越えるための “common understanding” を定義してくれる- 便利!
- そうはいってもデータのメモリ配置や「垣根」については依然意識しておく必要がある
- 不要なコピーを少なくする
- serialize/deserialize を少なくする("opaque handle" なるものを使う?)
- 一般的には「デカくて長寿命のデータ」は Rust の型として線形メモリに置き、その opaque handle を JS に公開することが多い
ライフゲームでもメモリの問題は重要。ライフゲームの世界 (universe) 全体を、時間経過ごとに総じてコピーしたくはないからね。
Rust Implementation
基本的には書いてある内容をそのまま書き写すので問題なさそう。
実装内容もさして難しくない。
- うっかり
#[wasm_bindgen]
attribute を書き忘れないよう注意。
これを忘れるとwasm-pack build
してもpkg/wasm_game_of_life.d.ts
等に反映されない。(やらかした) - 基本的には「private な関数を書く impl block」と「public に公開する関数を書く impl block(こっちにだけ
#[wasm_bindgen]
をつける」に分けることになりそう。 - わざわざ
self.to_string()
を呼び出すだけのrender()
関数を定義しているのも、おそらくto_string()
メソッドが JS 側に公開されないからだろう、と推測。まあ render という名前のほうが分かりやすいからというのもあるかも。
Rendering with JavaScript
こちらも実際に書かれている通りに写す。
前の工程で正しい pkg/wasm_game_of_life.d.ts
が生成されていれば、ちゃんとメソッド名の補完などが正しくきくようになる。素晴らしい開発体験だ。
ここまで出来るようになると、実際に動くものが見えるようになる。 思ったより早くできた。
インタラクティブに操作出来るようになるのはまだ先の話だが、動くものが見えるとやはり楽しい。
Testing Conway's Game of Life
Rust-generated WebAssembly functions cannot return borrowed references.
Try compiling the Rust-generated WebAssembly with the attribute and take a look at the errors you get.
これは頭の片隅にいれておくとよさそう。#[wasm_bindgen]
をつけるメソッドでは、借用を返せない。結構大きな制約だ。「とりあえず #[wasm_bindgen]
をつけとく」といった戦略はとれないことが分かる。
(ちょっと気になるのが、 rust-analyzer
がこのエラーを検出してくれなかったこと。wasm-pack build
でコンパイルして確かめる他なさそう)
テストは書けた。ちょっと引っかかったところがいくつかある。
- 特にチュートリアルには明記されてなかったが、
use wasm_bindgen_test::*;
は必要だった。- テンプレートに元から入っていたので、消さなければ良いはず。
-
extern crate ...
は無くても動いた。- これどういうときに必要なのかいまだに分かってない
-
--chrome
や--safari
などのwasm-pack test
実行時のオプション。- テストを実行するブラウザを選択できるらしい。
-
--chrome
を試すとError: chromedriver binaries are unavailable for this target
とのエラー。- もしかして M1 Mac には対応してなかったりする?
-
--safari
で実行するときは、予め Safari ブラウザから「リモートオートメーションを許可」しておく必要があった。以下設定手順:- Safari を開いた状態でメニューバーから Safari -> 環境設定 を開く
- 詳細->「メニューバーに"開発"メニューを表示」のチェックボックスをつける
- メニューバーに「開発」が表示されるので、そこを選択して「リモートオートメーションを許可」の部分をクリック
とりあえず、 --safari
オプションでのテストは実行できた。
-
--headless
をつけると自動的にブラウザが立ち上がりテストが実行され自動で閉じる。 -
--headless
をつけなければブラウザが立ち上がる。そこからhttp://127.0.0.1:8000
にアクセスすればテストが実行されるらしい。
まだブラウザとテストの関係がよく分かってないが、ブラウザの上で WASM を動かしているような感じなのだろうか?
とりあえずテストの実行手順は分かったのでよしとしよう。
Debugging
ブレークポイントを敷き、ログを吐いたりブラウザ上で一時停止したりできるようになった。
以下、注意点。
- 予めブラウザの開発者ツールを開いておいてから
localhost:8080
にアクセスする必要がありそう。
ライフゲームが起動してから開発者ツールを開こうとしても、うまく開けなかった。
Adding Interactivity
書いてある通りに実装すればうまくいく。
ここまで来ると眺めるだけでなくゲームの世界に干渉できるようになるので、普通に遊べる。
残るは
- Time Profiling
- Shrinking .wasm size
- Publishing to npm
だが、このあたりはまた必要になったときにやろう。