はじめに
何故RustでWebフロントエンド開発をするのか、まずはWebフロントエンド開発に詳しくない人向けにも、背景を簡単に紹介します。
HTML5時代なんて言われるようになって久しいですが、Webブラウザがリッチな表現力を持つにつれて、Webは単純なコンテンツ配信のプラットフォームからアプリケーションのプラットフォームへと成熟してきました。
Webフロントエンドアプリでは、基本的にはJavaScriptを使って開発を行います。
しかしながら、JavaScriptを直接書く場合には、言語機能に不足を感じることや、ブラウザに実装されている機能しか利用できないといった問題がありました。
最近では、それらの問題を克服するために、AltJSあるいはJavaScriptトランスパイラを使って、他のプログラミング言語からJavaScriptプログラムを出力するというアプローチが普及してきました。
リッチなWebフロントエンドアプリを実現する上でもう一つ問題となっていたのがJavaScriptの実行速度でした。
モダンブラウザのJavaScriptエンジンはよくチューニングされていて、JavaScriptは想像以上に高速に実行されるのですが、ゲームや科学技術計算等の分野では更なるパフォーマンスを要求されることがあります。
2012〜2013年頃には、Mozillaのasm.jsやGoogleのPNaCLなど、Webフロントエンドアプリの実行速度を改善するために様々な取り組みが始まりました。
そして、2015年になって各ブラウザベンダの協力のもと、WebAssemblyという新たなバイナリフォーマットの策定が始まりました。
WebAssemblyは、Webブラウザ上での高速実行を念頭に置いた新しい標準規格です。
WebAssemblyは2017年第一四半期にはパブリックプレビューが開始される予定です(参考: http://webassembly.org/roadmap/)。
ChromeやFirefoxでは、フラグを有効化することで既にWebAssemblyを試すことができます。
さて、前置きが長くなりましたがそろそろ本題に入りましょう。
これまでRustコミュニティはRustのWebAssembly対応に積極的に取り組んできており、現時点では試験的にですがRustからasm.jsとWebAssemblyへの出力がすでに可能となっています。
RustをWebフロントエンド開発に使うことで、高い安全性や生産性を得られる上に、asm.jsやWebAssemblyをベースにした高い実行性能も得ることができるのです。
本稿では、Rustからasm.jsとWebAssemblyにコンパイルする手順について極簡単にですがご紹介します。
準備
rustupとEmscriptenを使います。
以下のページを参考に、セットアップを行います。
コマンドはリンク先からの抜粋です。
まずはrustupのインストールです。
$ curl -L https://sh.rustup.rs | sh -s -- -y --default-toolchain=nightly
$ source ~/.cargo/env
$ rustup target add asmjs-unknown-emscripten
$ rustup target add wasm32-unknown-emscripten
次にEmscriptenのインストールです。
$ curl -O https://s3.amazonaws.com/mozilla-games/emscripten/releases/emsdk-portable.tar.gz
$ tar -xzf emsdk-portable.tar.gz
$ source emsdk_portable/emsdk_env.sh
$ emsdk update
$ emsdk install sdk-incoming-64bit
$ emsdk activate sdk-incoming-64bit
私はmacOS Sierra上で動作確認しています。
コンパイルと実行
現状ではasm.jsとWebAssemblyで実行方法が少し異なっているのでそれぞれ紹介します。
実行例は以下のページにも掲載されています。
asm.js
以下のコマンドでプロジェクトを作成し、コンパイルしています。
$ cargo new --bin hello
$ cd hello
$ cargo build --target asmjs-unknown-emscripten
Rustのソースコードは以下の通りです。
fn main() {
println!("Hello, world!");
}
コンパイルに成功すると target/asmjs-unknown-emscripten/debug/hello.js
が生成されます。
カレントディレクトリに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>
Emscriptenを使うためにPython2が使えるはずなので、それでWebサーバーを立ち上げます。
$ python2 -m SimpleHTTPServer
http://localhost:8000/asmjs.html にアクセスし、Developer Toolを開くとConsoleに Hello, world!
と表示されているかと思います。
WebAssembly
asm.js版で既にプロジェクトが作成されているものとします。
以下のコマンドでWebAssemblyへのコンパイルを行います。
$ cargo build --target wasm32-unknown-emscripten
コンパイルに成功すると target/wasm32-unknown-emscripten/debug/hello.js
が生成されます。
また、target/wasm32-unknown-emscripten/debug/deps
の中に拡張子が .wasm
のファイルが生成されています。
私の場合は hello-7d3b782b5c65ec91.wasm
という感じでした。
こちらがWebAssemblyバイナリの本体なので以下のような感じでコピーしてきましょう。
$ cp target/wasm32-unknown-emscripten/debug/deps/*.wasm hello.wasm
そして以下のようなHTMLファイルを作成します。
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8" />
<title>Rust to WebAssembly example</title>
</head>
<body>
<script>
var Module = {}
fetch('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>
現時点ではバイナリの互換性問題もあって動作できる環境が多くなさそうなのですが、Chrome Canary (Version 57)で動作確認できました。
chrome://flags からExperimental WebAssemblyをEnableにして一度再起動してください。
そして、asm.jsと同様にWebサーバーを立ち上げ、http://localhost:8000/wasm.html にアクセスします。
おわりに
本稿ではRustのHello Worldプログラムをasm.jsとWebAssemblyにコンパイルして、Webブラウザで動かす方法を紹介しました。
これだけでは面白くありませんが、webplatformというcrateでDOM操作をしたり、Emscriptenを使ってOpenGL ES 2.0のプログラムをWebGL対応させることもできます。
また、Rustで開発した関数をJavaScriptから呼び出すことも可能です。
JavaScriptやブラウザAPIとのやり取りなど、まだまだ課題は山積みだと思いますが、RustのWebAssembly対応はC/C++に次いで進んでいると言っていいでしょう。
RustでWebフロントエンド開発ができるようになる近い将来に備えて、RustやWebフロントエンド技術を学んでおくのはいかがでしょうか。