初めてRustでWebAssemblyするときに紹介したいチュートリアル (入門)

こんにちは、kamyknと申します。

最近趣味の範囲ですがRustでWebAssemblyを試していています。
というわけで最近Rustに関して記事を漁っていたところRust × WebAssemblyに関するチュートリアルで良いものを見つけましたのでまとめてみました。

比較的新しい内容でRustによるWebAssemblyプログラミングができ、JSから数値以外もWebAssemblyに渡せたり、npmのエコシステムに乗せたりと僕が知りたかったことがすんなり体験することができました:laughing:

この記事では上記に加えてブラウザで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バイナリビルド

さらに元記事ではさらにライフゲームを作ってみたり、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のセットアップでコマンドが紹介されています。
（コメントで指摘いただきまして、間違いがありましたので内容を修正させていただきました:bow:）

```
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

```bash
$ 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)](https://ja.wikipedia.org/wiki/%E3%83%A9%E3%82%A4%E3%83%95%E3%82%B2%E3%83%BC%E3%83%A0)

### 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のコードを生成

ということで、やってくれることがわかったので早速使ってみます。

```bash
$ 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として動くようにしていきます。

プロジェクトのルートディレクトリで、

```bash
$ npm init wasm-app www
npx: 1個のパッケージを2.51秒でインストールしました。
🦀 Rust + 🕸 Wasm = ❤
```
を実行します。(絵文字ナイスですね！)
これは[wamp-appテンプレート](https://github.com/rustwasm/create-wasm-app)に基づいてnpm initしています。


```bash
$ 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```でビルドのみ実行

```json:wasm-game-of-life/www/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ファイルです。

```html:wasm-game-of-life/www/index.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の部分)

```js:wasm-game-of-life/www/index.js
import * as wasm from "hello-wasm-pack";

wasm.greet();
```

### Install the dependencies
上記で作成されたnpmでdependenciesになっているライブラリを読み込みます。
これは、```wasm-game-of-life/www```で実行します

```bash
$ 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```をします。

```bash
$ 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`または自分の付けた名前)を指定します。

```js:wasm-game-of-life/www/index.js
import * as wasm from "wasm-game-of-life";

wasm.greet();
```

### Serving Locally
(さぁ、お疲れ様です。いよいよブラウザで実行確認するステップです。)
```wasm-game-of-life/www```ディレクトリで下記のコマンドを実行してサーバーを立ち上げます。
なお、ブラウザで確認できるURLは`http://localhost:8080/`だそうです。

```bash
$ 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を参照してみる例)
<img width="588" alt="スクリーンショット 2018-10-11 1.35.36.png" src="https://qiita-image-store.s3.amazonaws.com/0/73446/6db07791-ce55-9fe0-44a1-a33235b789ab.png">

### Exercises
今回wasmバイナリを生成した中のファイル、```wasm-game-of-life/src/lib.rs```を修正してJSから文字列を渡してみましょう。

なお、Rustは静的型付け言語なのですが、今回使う型は`&str`という参照を持った文字列スライスを使ってみます。

```rust:wasm-game-of-life/src/lib.rs
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をビルドし、

```bash
$ wasm-pack build
```

次にJSを編集します

```js:wasm-game-of-life/www/index.js
import * as wasm from "wasm-game-of-life";
  
wasm.greet('kamykn');
```

サーバー起動(またはwebpackでbuild `npm run build`)

```
$ npm run start
```

(↓またしても直接distを参照してみる例)
<img width="586" alt="スクリーンショット 2018-10-11 1.54.36.png" src="https://qiita-image-store.s3.amazonaws.com/0/73446/43ce7f27-3e91-e611-5d50-a077f3462342.png">

`Hello kamykn!!` が確認できたということで、無事にJSからWebAssemblyに文字列を渡すことができました！:laughing:

## 今回紹介するチュートリアルはここまで
これでnpmのエコシステムに乗ったRustによるWebAssemblyプログラミングを体験することができましたね。
また、今回のチュートリアルではJSから文字列を渡しているところにも注目です。
これは`wasm-bindgen`ライブラリによるJSとWebAssembly間の架け橋による恩恵です。
これまでは自分で同様の処理を書くとなると、メモリ上のJSのUint8ArrayをRustにアドレスを渡して〜といった具合だったので、`wasm-bindgen`のおかげで大分楽になりました。

また今回、Rustという言語についても少し触れることもできました。
Rustという言語自体も借用やライフタイムなどの概念を持った興味深い言語になっていますので、もし初めて触る方なのであればRustについても軽く触ることができて一石二鳥ですね:v:
(もし興味があれば借用チェッカーに怒られながらの開発に足を突っ込んでみてはいかがでしょうか…！:laughing:)

このあと実際の開発に活かすとするならば、個人的にはまずは計算量の重めなところのWebAssemblyによる置き換えが第一歩かなと思いました。
今回紹介した記事はとても素晴らしい記事ですので、気になった方はぜひ元記事の方も見てみてはいかがでしょうか。
元記事の方では更にもう一歩踏み込んだチュートリアルに進むことができます(が、ライフゲームはちょっとチュートリアルとしてはちょっと重いかなとも思ったり:sweat_smile:)。

何はともあれ、手軽にRustによるWebAssembly体験をさせてくれた元記事様、大変ありがとうございましたm(_ _)m

↓↓偉大なるネタ元
## Rust 🦀 and WebAssembly 🕸
https://rustwasm.github.io/book/introduction.html