RustをWASMに変換してブラウザ上で動かす

これは [WebAssembly Advent Calendar 2017](https://qiita.com/advent-calendar/2017/webassembly) 19日目の記事です。

# はじめに

この記事ではRustのプログラムをWebブラウザで動かす方法を2つご紹介します。

1. asm.jsにコンパイルする方法
2. WebAssembly（以下、WASM）にコンパイルする方法

# 事前準備

## Rustコンパイラ（rustup）のインストール

```bash
$ curl https://sh.rustup.rs -sSf | sh

# 環境に合わせて .zshrc や .bashrc に以下を追記
$ source ~/.cargo/env
```

### トラブルシューティング

先にhomebrew経由でrustをインストールしていると、下記のようなエラーが出力され、rustupのインストールに失敗します。

```bash:rustを既にインストールしている場合のエラーメッセージ
$ curl https://sh.rustup.rs -sSf | sh
info: downloading installer
error: it looks like you have an existing installation of Rust at:
error: /usr/local/bin
error: rustup cannot be installed alongside Rust. Please uninstall first
error: if this is what you want, restart the installation with `-y'
error: cannot install while Rust is installed
```

その場合は、`brew uninstall --force rust` でhomebrew経由のrustをアンインストールしましょう。

```bash
$ brew uninstall --force rust
```

## Emscripten のインストール

```bash
$ git clone https://github.com/juj/emsdk.git
$ cd emsdk

# 環境に合わせて .zshrc や .bashrc に以下を追記
$ source ~/emsdk/emsdk_env.sh

# かなり重たいコンパイルが走るので注意しましょう。手元の環境では数十分かかりました。
$ ./emsdk install sdk-incoming-64bit binaryen-master-64bit

$ ./emsdk activate sdk-incoming-64bit binaryen-master-64bit
```

### トラブルシューティング

cmakeがインストールされていないと、下記のエラーが出力されます。

```bash:cmakeが無い場合のエラーメッセージ
[Errno 2] No such file or directory
Could not run CMake, perhaps it has not been installed?
Installing this package requires CMake. Get it via a OSX package manager (Homebrew: "brew install cmake", or MacPorts: "sudo port install cmake"), or from http://www.cmake.org/
Installation failed!
```

その場合は、`brew install cmake` しましょう。

```bash
$ brew install cmake
```

# RustのプログラムをWebブラウザで動かす

## プロジェクトの作成

まずは、cargoコマンドを使って、「Hello, world!」と出力するプロジェクトを作成します。

```bash
$ cargo new --bin hello
$ cd hello

# ファイルの中身を確認
$ cat src/main.rs
fn main() {
    println!("Hello, world!");
}

# ビルド+実行
$ cargo run
Hello, world!
```

このプロジェクトをWebブラウザで動かすには、asm.jsにコンパイルする方法と、WebAssembly（以下、WASM）にコンパイルする方法の2通りがあります。
それぞれ順に紹介します。

## 方法1：asm.jsをターゲットにビルド

```bash
# asm.jsをターゲットアーキテクチャに追加
$ rustup target add asmjs-unknown-emscripten

# asm.jsをtargetにビルド
$ cargo build --target asmjs-unknown-emscripten

# ビルドの生成物を確認
$ ls target/asmjs-unknown-emscripten/debug
build       deps        examples    hello.d     hello.js    incremental native
```

### ams.jsをブラウザ上で動かす

プロジェクトの直下に、次のようなhtmlファイルを作りましょう。

```html:amsjs.html
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8"/>
        <title>Rust to asm.js example</title>
    </head>
    <body>
        <script src="target/asmjs-unknown-emscripten/debug/hello.js"></script>
    </body>
</html>
```

ローカルでHTTPサーバを立てます。これはRubyでサーバを立てる例です。

```bash
$ ruby -run -e httpd . -p 3000
```

http://localhost:3000/asmjs.html にブラウザでアクセスし、開発コンソールを表示しましょう。
成功すれば「Hello, world!」と表示されます。

![amsjs.html](https://qiita-image-store.s3.amazonaws.com/0/17718/4975caff-7fba-c673-dfea-89b7ada70164.png)

## 方法2：WASMをターゲットにビルド

ターゲット名が `asmjs-unknown-emscripten` -> `wasm32-unknown-emscripten` に変わったことを除けば、asm.jsと全く同じ手順です。

```bash
# WASMをターゲットアーキテクチャに追加
$ rustup target add wasm32-unknown-emscripten

# WASMをtargetにビルド
$ cargo build --target=wasm32-unknown-emscripten

# ビルドの生成物を確認
$ ls target/wasm32-unknown-emscripten/debug
build       deps        examples    hello.d     hello.js    hello.wasm  incremental native
```

### WASMをブラウザ上で動かす

asm.jsとほぼ同様の手順です。WASMでは次ようなhtmlを作成します。

```html:wasm.html
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>Rust to WebAssembly example</title>
    </head>
    <body>
        <script>
        var Module = {}
        fetch('target/wasm32-unknown-emscripten/debug/hello.wasm')
            .then((response) => response.arrayBuffer())
            .then((buffer) => {
                Module.wasmBinary = buffer
                var script = document.createElement('script')
                script.src = "target/wasm32-unknown-emscripten/debug/hello.js"
                document.body.appendChild(script)
            })
        </script>
    </body>
</html>
```

http://localhost:3000/wasm.html にブラウザでアクセスし、開発コンソールを表示しましょう。
成功すれば「Hello, world!」と表示されます。

![wasm.html](https://qiita-image-store.s3.amazonaws.com/0/17718/17828c81-106d-9713-55d0-a8f4323d2ca5.png)

# 参考記事

以下の記事を大変参考にさせていただきました。本当にありがとうございます！

- [Rust から WASM32 を出力するための準備](https://qiita.com/chikoski/items/6c9f24dc4a825c437e78)
- [RustでWebフロントエンド開発](https://qiita.com/_likr/items/daf46d6f66bc31cc4810)
- [rustup で Rust コンパイラーを簡単インストール](https://qiita.com/chikoski/items/b6461367e8c3875bb235)

# 次回予告

今年の秋に開催されたレイトレ合宿5‽で、Rustを使って物理ベースレンダラーを自作しました。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">レイトレ合宿5‽に参加して、Rustでパストレーシングを実装しました。<br>Rust初心者でしたが、なんとか頑張りました。<br>高速化の取り組みについても書いたので、ぜひ読んで下さい！ <a href="https://twitter.com/hashtag/%E3%83%AC%E3%82%A4%E3%83%88%E3%83%AC%E5%90%88%E5%AE%BF?src=hash&amp;ref_src=twsrc%5Etfw">#レイトレ合宿</a><a href="https://t.co/L2bOfQMds7">https://t.co/L2bOfQMds7</a></p>&mdash; がむ😇 (@gam0022) <a href="https://twitter.com/gam0022/status/914639288058265600?ref_src=twsrc%5Etfw">2017年10月1日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

このレンダラーをぜひ多くの人に動かしてもらいたくWebに移植しようと考えたのですが、その事前調査として簡単なRustのプログラムをブラウザ上で動かすまでに試行錯誤しました。そこで、今回はその手順をまとめて記事にさせていただきました。

次回は「自作のRustによる物理ベースレンダラーをWebに移植した話」について書きたいと思います。