はじめに
RustにもWebAssembly(以降wasmと呼ぶ)にも興味はあるが、馴染みがない今日このごろ。Rustでwasmをやってみれば一石二鳥なんじゃあないか!?と思って始めてみました。
やってみると、Hello World的なものは意外とすぐにできたのですが、 AnimationLoop しようとしたり、データをHTTPでFetchしようとしたりするとなかなか尋常じゃなく躓きました(それぞれ1日くらい解決までかかった...)。
Rustも発展途上でちょいちょい言語仕様も変わっているので、過去のWebの記事通りでは動かなかったり、そもそもRustへの理解が足りてなかったりするのですが、自分用のメモ兼もしかしたら誰かの参考になるかもしれないので、何回かに分けてざっくりこれまで理解したことを書いておこうと思います。
Version
これから示すコードが動くかどうかについて、RustやcrateのVersionはおそらくとても大事です。
OSはMacでもWindowsでもLinuxでもあまり差はないと思います。
ブラウザもいわゆるモダンなブラウザなら大丈夫だと思います。IEだとダメかもしれない。Edgeはどうなのかな。(<script type="module">
みたいなので動くかどうかで、うまくいかない場合は他の方法を使えばwasm自体は動くと思います)。
- rustc 1.45.2 (d3fb005a3 2020-07-31): Install方法
- wasm-pack 0.9.1: Install方法
- crate
- wasm-bindgen = "0.2.67"
- js-sys = "0.3.44"
- web-sys = "0.3.44"
- MacOS: 10.15.6
- Google Chrome: 84.0.4147.105
- Python3 (単純なHttpServer用)
consoleにログを出してみる
まあ、まずはとにかく動くようにするところからですが、それは拍子抜けするほど簡単でした。端的にいうと以下のファイルを用意しておけばOKです。(Makefileは別にいらんけど)。
.
├── Cargo.toml
├── Makefile
├── index.html
└── src
└── lib.rs
コード全体はこれです。一つずつみていきます。
Cargo.toml
主に使用するcrateなどの宣言をするところですが、最初はとにかくこう書いておけ、という感じではあります。以下の3つのcrateを使用しますが、私の理解では、
-
js-sys
: JavaScriptのいろいろをRustで使うための何か -
web-sys
: ブラウザの機能やDOM周りをRustで使うための何か -
wasm-bindgen
: JavaScript <-> wasm のInterface周りをやってくれる何か
となっています。 features
というのは、余分なコードを取り込まないように使う機能(コード)だけをbuildしましょうということで分けてあるんだと思います(ふんいき談)。
[package]
name = "rust_wasm_example"
version = "0.1.0"
authors = ["mokemokechicken"]
edition = "2018"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2.67"
js-sys = "0.3.44"
[dependencies.web-sys]
version = "0.3.44"
features = [
'console',
]
ここで
[dependencies.web-sys]
version = "0.3.44"
features = [
'console',
]
という部分は、[dependencies]
の中に
web-sys = {"version" = "0.3.44", features=['console']}
と書いても同じなのですが、今後このweb-sys
については features
がどんどん増えていくので、上記のような書き方にしておくほうが見やすい気がします(意味は同じ、、だと思う)。
大事なのは2つ同時に書いてしまうと、エラーになるので注意が必要ということです(これで10分くらい悩んだし...)。
src/lib.rs
Rust本体。ライブラリ系は lib.rs
が起点になるというのが規約となっているようです。
use wasm_bindgen::prelude::*;
use web_sys::console::log_1;
fn log(s: &String) {
log_1(&JsValue::from(s));
}
#[wasm_bindgen]
pub fn output_log(s: &str) {
log(&format!("Hello {}", s));
}
ポイントはやはり #[wasm_bindgen]
の部分と use web_sys::console::log_1
かな。
#[wasm_bindgen]
となっているfunctionはJavaScriptから呼べるようになります。指定できる引数や返り値は結構なんでもいけます(書き方などは面倒なのもありますが)。文字列だとこんな感じです。
console::log_1
というのは引数を1つとる JSでいう console.log()
みたいなものっぽいです。 log_2
, log_3
というのもあります。
ログはやはりよく使うことになると思いますが、 &JsValue::from(s)
みたいに JsValue
という JavaScript世界の型
にしてあげる必要があるようです。ので、簡単な関数を作っておくと便利でした。String
型を作るには format!("{}, {}", val1, val2)
みたいなマクロがだいたいどんなときでも使えるのでまずはこれだけ覚えておけばログには困らないという気がします(Rust歴数日の人間の言うことは信じてはいけない)。
※ 実はここでは js-sys
は使ってないから Cargo.toml には書かなくて良かったのですが、どうせまあ今後使うので...
一応この Cargo.toml
と lib.rs
だけあればwasmのビルドができてしまいます。
Makefile
ビルドコマンドを覚えるのが面倒なので、Makefileに書いておきます。
-
build
は wasm ビルド用。--target web
がポイント。他にも方法があるがこれが余計なツールを使わずに済んで好みだった。 -
serve
は wasmモジュールをLocalfileから読み込んではくれない(たぶん)のでHttpServerが必要です。なんでも良いのですがここでは PythonのSimpleHTTPServer
を使います。
build:
wasm-pack build --target web
serve:
echo "http://localhost:8082/"
python -m SimpleHTTPServer 8082
clean:
rm -rf pkg target
Buildしてみる
make build
とします。成功すると pkg/
, target/
というDIRができると思います。使うのは pkg/
の方です。
これが通れば、あとは簡単です。
index.html
このwasm-bindgenのGuideから借用します。
<script type="module">
なんてあるんですね。知らなかった。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
</head>
<body>
<!-- Note the usage of `type=module` here as this is an ES6 module -->
<script type="module">
import init, * as wasm from './pkg/rust_wasm_example.js';
async function run() {
await init();
wasm.output_log("Wasm!");
}
run();
</script>
</body>
</html>
HTTPServerを起動してアクセス
make serve
とします。
% make serve
echo "http://localhost:8082/"
http://localhost:8082/
python -m SimpleHTTPServer 8082
Serving HTTP on 0.0.0.0 port 8082 ...
こんな表示になって、http://localhost:8082/ にアクセスしてみて、consoleを開いてログが表示されていれば成功となります。
さいごに
いちいち書くと長いですね。
でも、わりと簡単にここまではできるんだなーと思いました。
だがしかし(Rust初心者にとって)大変なのはこれからでした。