こんにちは、kamyknと申します。
最近趣味の範囲ですがRustでWebAssemblyを試していています。
というわけで最近Rustに関して記事を漁っていたところRust × WebAssemblyに関するチュートリアルで良いものを見つけましたのでまとめてみました。
比較的新しい内容でRustによるWebAssemblyプログラミングができ、JSから数値以外もWebAssemblyに渡せたり、npmのエコシステムに乗せたりと僕が知りたかったことがすんなり体験することができました
この記事では上記に加えてブラウザでJSから渡す文字列とalert()
ファンクションを使ってWebAssemblyを通じてHello World!ができるまでをまとめます。
この記事の要約
こちらの記事は下記のGitHub Pagesの
https://rustwasm.github.io/book/introduction.html
特に『5.2. Hello, World!』を要訳した記事です
https://rustwasm.github.io/book/game-of-life/hello-world.html
このチュートリアルでできるようになること
- この記事で取り扱う範囲
- WebAssemblyからJSの
alert()
を使ったHello World. - 必要なライブラリ(RustとJS周り)の紹介
- WebpackのJSからの簡単な取り扱い方について(import周り)
- JSからWebAssemblyへの値の受け渡し(数値以外も可)
- Emscriptenを使わないWebAssemblyバイナリビルド
- WebAssemblyからJSの
さらに元記事ではさらにライフゲームを作ってみたり、npmのパッケージとして登録するところまでをゴールとしているみたいです。
チュートリアルで出てくるRust以外の知識
このチュートリアルではRust以外の周辺の技術として、下記の知識があるとより理解が進むかと思います。
- node.js / npm
- Webpack
これはWebpackでwasmバイナリファイルをnpmのエコシステムに乗っける(そして元記事のゴールであるnpmにpackage登録して配布する)為に必要になります。
本編
Setup
まずは環境とツールを揃えていきます。
元記事は下記リンクのページになります。
https://rustwasm.github.io/book/game-of-life/setup.html#cargo-generate
The Rust Toolchain
rustup
、rustc
、cargo
がある環境を用意します。
元記事にはありませんが、Rustの公式では下記の通りです
(https://www.rust-lang.org/ja-JP/install.html より)
curl https://sh.rustup.rs -sSf | sh
このチュートリアルではrust 1.30.0が必要になります。
現在(2018年10月10日)、nightlyでも1.29なので更に新しいbetaである必要があります。
現在betaが1.30なのでbeta及びそれよりも進んだnightlyが利用可能です。
なお、元記事ではbetaのセットアップでコマンドが紹介されています。
(コメントで指摘いただきまして、間違いがありましたので内容を修正させていただきました)
rustup default beta
※ ちなみにRustのマイナーバージョンはガンガン上がっていきます…!
wasm-pack
RustでWebAssemblyする際に手助けしてくれるようなコマンドを提供しているツールです。
https://github.com/rustwasm/wasm-pack
[公式] https://rustwasm.github.io/wasm-pack/installer/
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
ちなみにこのツールがrustのバージョンを1.30.0 を要求しているので、このチュートリアルではnightlyかbetaのものが必要になっています。
なお、wasm-packはwasm-bindgenを使うことが前提となっているかと思います。
cargo-generate
これはテンプレートとなるリポジトリを指定すると、関連するものをいろいろと揃えてくれるツールのようです。
初期構築でお世話になります。
https://github.com/ashleygwilliams/cargo-generate
$ cargo install cargo-generate
npm
npm init で wasm-appが指定できればlatestでなくても大丈夫だと思います。
npmを使うにもnode.jsが必要です。
node.js / npm は他にインストール記事が豊富なので、そちらに任せることにしてここでは割愛します。
元記事には下記のインストールコマンドだけが書いてあります。
$ npm install npm@latest -g
Hello, World!
さぁ、これからWebAssemblyでHello World!をしていきましょう。
ここからの内容の元記事は下記リンクのページになります。
https://rustwasm.github.io/book/game-of-life/hello-world.html
Clone the Project Template
$ cargo generate --git https://github.com/rustwasm/wasm-pack-template
Project Name: wasm-game-of-life # ここは自分で決めたプロジェクト名を入力する
Creating project called `wasm-game-of-life`...
Done! New project created /var/www/test/wasm-game-of-life
元記事内では
This should prompt you for the new project's name. We will use "wasm-game-of-life".
(新しいプロジェクトの名前の入力をしてね。この記事ではwasm-game-of-lifeにするよ)
とのことなので、名前は実は何でも良かったりしますが、以後この記事におけるwasm-game-of-life
という単語は自分のProject Nameで置き換えてください。
※なぜwasm-game-of-life
なのかについては、元記事のチュートリアルでgame of life(ライフゲーム)というゲームを作るからですね。私の本記事では簡単にJSからWebAssemblyに文字列を渡してHello Worldするまでを対象としますので、気になる方は元記事を参照してみてください。
※ちなみにライフゲームはこんな感じのゲームです(Wikipedia)
What's Inside
元記事では階層だけが書いてあるんですが、実際に確認するときにはtreeコマンドなどを使いましょう。
$ tree
.
├ Cargo.toml
├ LICENSE_APACHE
├ LICENSE_MIT
├ README.md
└ src
├ lib.rs
└── utils.rs
Build the Project
wasm-packによって下記のビルドステップをオーケストレーションしているぜとのこと。
- Rust 1.30以降とwasm32-unknown-unknownターゲットがインストールされていることを確保
- cargoによってRustをWebAssembly (拡張子が.wasm) バイナリにコンパイルする
- Rustで作成されたWebAssemblyから使える(wasm-bindgenにより生成される)JavaScript APIのコードを生成
ということで、やってくれることがわかったので早速使ってみます。
$ wasm-pack build
[1/9] Checking `rustc` version...
[2/9] Checking crate configuration...
[3/9] Adding WASM target...
[4/9] Compiling to WASM...
[5/9] Creating a pkg directory...
[6/9] Writing a package.json...
:-) [WARN]: Field 'description' is missing from Cargo.toml. It is not necessary, but recommended
:-) [WARN]: Field 'repository' is missing from Cargo.toml. It is not necessary, but recommended
:-) [WARN]: Field 'license' is missing from Cargo.toml. It is not necessary, but recommended
[7/9] Copying over your README...
[8/9] Installing wasm-bindgen...
[9/9] Running WASM-bindgen...
:-) Done in 3 minutes
| :-) Your wasm pkg is ready to publish at "/var/www/test/wasm-game-of-life/pkg".
$ tree pkg
pkg
├ README.md
├ package.json
├ wasm_test.d.ts
├ wasm_test.js
└ wasm_test_bg.wasm
README.md以外は完全に新規で作成されるらしいです。
なお、通常cargo build --target=wasm32-unknown-unknown
などすると、targetディレクトリの深い階層にバイナリが置かれますが、
wasm-pack
ではpkg配下の浅い階層に置いてくれます。
以下、簡単な説明です。
-
wasm-game-of-life/pkg/wasm_game_of_life_bg.wasm
- greetファンクションを含むWebAssemblyのバイナリです。
-
wasm-game-of-life/pkg/wasm_game_of_life.js
- wasm-bindgenにより作成されるRustとJS間のやり取りができるようになる内容などを含むJSファイルです
-
wasm-game-of-life/pkg/wasm_game_of_life.d.ts
- TypeScript用の宣言が書かれたファイルです
-
wasm-game-of-life/pkg/package.json
- JavaScriptのツール連携用またはnpmに登録するようとのこと
Putting it into a Web Page
WebPage用のディレクトリを作ってその中でJSを書き、WebPageとして動くようにしていきます。
プロジェクトのルートディレクトリで、
$ npm init wasm-app www
npx: 1個のパッケージを2.51秒でインストールしました。
🦀 Rust + 🕸 Wasm = ❤
を実行します。(絵文字ナイスですね!)
これはwamp-appテンプレートに基づいてnpm initしています。
$ tree www
www
├ LICENSE-APACHE
├ LICENSE-MIT
├ README.md
├ bootstrap.js
├ index.html
├ index.js
├ package-lock.json
├ package.json
└── webpack.config.js
-
wasm-game-of-life/www/package.json
- webpackで初期設定済み(dependenciesやscriptなど)のpackage.jsonです。
-
npm run start
でサーバー立ち上げ -
npm run build
でビルドのみ実行
-
- webpackで初期設定済み(dependenciesやscriptなど)のpackage.jsonです。
$ cat package.json
~~ 中略 ~~
"scripts": {
"build": "webpack --config webpack.config.js",
"start": "webpack-dev-server"
},
~~ 中略 ~~
"devDependencies": {
"hello-wasm-pack": "^0.1.0",
"webpack": "^4.16.3",
"webpack-cli": "^3.1.0",
"webpack-dev-server": "^3.1.5",
"copy-webpack-plugin": "^4.5.2"
}
-
wasm-game-of-life/www/webpack.config.js
- webpackコマンドで使われる設定が書いてあります。
-
wasm-game-of-life/www/index.html
- 変哲もないHTMLファイルです。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello wasm-pack!</title>
</head>
<body>
<script src="./bootstrap.js"></script>
</body>
</html>
-
wasm-game-of-life/www/index.js
- WebAssemblyのバイナリをimportしてJSから実行します
- あとで書き換えます。(hello-wasm-packの部分)
import * as wasm from "hello-wasm-pack";
wasm.greet();
Install the dependencies
上記で作成されたnpmでdependenciesになっているライブラリを読み込みます。
これは、wasm-game-of-life/www
で実行します
$ npm install
Using our Local wasm-game-of-life Package in www
JSファイルwasm-game-of-life/www/index.js
でhello-wasm-pack
となっていた箇所を書き換えてwasm-pack build
で生成されたwasmバイナリを使いましょう。
ここでnpm link
を使います。
npm link
は平たく言うとローカルでnpmパッケージがあたかもnpmからインストールしたライブラリのように使えるようにする作業です。
まずはwasm-game-of-life/pkg
でnpm link
をします。
$ npm link
次にnpm link
されたwasm-game-of-life
をwww
パッケージから参照できるように把握させます。
wasm-game-of-life/www
階層で下記のコマンドを実行します。
$ npm link wasm-game-of-life
最後にwasm-game-of-life/www/index.js
を書き換えます
import先を自分のプロジェクト(wasm-game-of-life
または自分の付けた名前)を指定します。
import * as wasm from "wasm-game-of-life";
wasm.greet();
Serving Locally
(さぁ、お疲れ様です。いよいよブラウザで実行確認するステップです。)
wasm-game-of-life/www
ディレクトリで下記のコマンドを実行してサーバーを立ち上げます。
なお、ブラウザで確認できるURLはhttp://localhost:8080/
だそうです。
$ npm run start
npm run start
はwebpack-dev-serverの実行でしたね。(→ package.jsonのscriptsの内容です)
ちなみにnpm run start
でwebpack-dev-serverを立ち上げなくてもnpm run build
するとwasm-game-of-life/www/dist
に一通りファイルが生成されますので、直接dist配下をブラウザで開いても問題ないです。
(↓手元でDockerで環境構築してしまったので直接distを参照してみる例)
Exercises
今回wasmバイナリを生成した中のファイル、wasm-game-of-life/src/lib.rs
を修正してJSから文字列を渡してみましょう。
なお、Rustは静的型付け言語なのですが、今回使う型は&str
という参照を持った文字列スライスを使ってみます。
extern crate cfg_if;
extern crate wasm_bindgen;
mod utils;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern {
fn alert(s: &str);
}
// このfunctionを変更します
#[wasm_bindgen]
pub fn greet(name: &str) {
alert(&format!("Hello, {}!", name));
}
wasmをビルドし、
$ wasm-pack build
次にJSを編集します
import * as wasm from "wasm-game-of-life";
wasm.greet('kamykn');
サーバー起動(またはwebpackでbuild npm run build
)
$ npm run start
Hello kamykn!!
が確認できたということで、無事にJSからWebAssemblyに文字列を渡すことができました!
今回紹介するチュートリアルはここまで
これでnpmのエコシステムに乗ったRustによるWebAssemblyプログラミングを体験することができましたね。
また、今回のチュートリアルではJSから文字列を渡しているところにも注目です。
これはwasm-bindgen
ライブラリによるJSとWebAssembly間の架け橋による恩恵です。
これまでは自分で同様の処理を書くとなると、メモリ上のJSのUint8ArrayをRustにアドレスを渡して〜といった具合だったので、wasm-bindgen
のおかげで大分楽になりました。
また今回、Rustという言語についても少し触れることもできました。
Rustという言語自体も借用やライフタイムなどの概念を持った興味深い言語になっていますので、もし初めて触る方なのであればRustについても軽く触ることができて一石二鳥ですね
(もし興味があれば借用チェッカーに怒られながらの開発に足を突っ込んでみてはいかがでしょうか…!)
このあと実際の開発に活かすとするならば、個人的にはまずは計算量の重めなところのWebAssemblyによる置き換えが第一歩かなと思いました。
今回紹介した記事はとても素晴らしい記事ですので、気になった方はぜひ元記事の方も見てみてはいかがでしょうか。
元記事の方では更にもう一歩踏み込んだチュートリアルに進むことができます(が、ライフゲームはちょっとチュートリアルとしてはちょっと重いかなとも思ったり)。
何はともあれ、手軽にRustによるWebAssembly体験をさせてくれた元記事様、大変ありがとうございましたm(_ _)m
↓↓偉大なるネタ元
Rust 🦀 and WebAssembly 🕸