WebAssemblyとは
javascriptのように、ウェブブラウザ上で動くプログラミング言語です。
ウェブブラウザ上で動作するプログラミング言語といえば、javascriptが代表格だと思います。(というか他に思いつかない)
ですが、元々文字だけの静的なページにアニメーションなど、動的にwebページを装飾したいという動機で作られたスクリプト言語なので、開発が複雑かつ大規模になるにつれ、実行速度に影響が出るという弱点を抱えています。
その問題を背景として登場したのがWebAssembly(WASM)です。
この言語の特徴として挙げられるのは、WebAssemblyという言語を直接記述するわけではなく、C/C++/Rustなど、他言語からコンパイルすることによって生成される、という点です。
つまり他言語で培われた資源をそのまま活用でき、生成されるコードはアセンブリのような低水準言語になるので実行速度が速いという、ちょうどjavascriptの苦手な部分を補完するような立ち位置となっています。
Rust -> WebAssembly
RustからWebAssemblyへ変換してブラウザ上で動かしてみたいと思います。
Rustのインストール
$ brew install rust
WebAssembly用のbuildpackをインストール
$ cargo install wasm-pack
cargoはRustのパッケージマネージャ兼ビルドシステムである便利屋さんです。
テンプレートをcargoを使って生成する
$ cargo generate --git https://github.com/rustwasm/wasm-pack-template.git --name rust2wasm
リポジトリはこちらです。
もしcargo-generate
のインストールが済んでいない場合は
$ cargo install cargo-generate
をしてください。
生成されたファイルの中で、src/lib.rs
がプログラムのメイン部分になります。
mod utils;
use wasm_bindgen::prelude::*;
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
// allocator.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
#[wasm_bindgen]
extern {
fn alert(s: &str);
}
#[wasm_bindgen]
pub fn greet() {
alert("Hello, rust2wasm!");
}
Rustからjsのalert関数を呼び出す想定です。
wasm_bindgen
によってjsからRustで書かれた関数を呼び出したり、Rustがjsの例外をキャッチできるようになったり、と橋渡し役を担っているライブラリです。
pub fn greet() {
alert("Hello, rust2wasm!");
}
定義したgreetをjavascriptから呼び出すことでalertを出すイメージです。
ビルドする
$ wasm-pack build
ビルドをすると、プロジェクトにpkg
というディレクトリが生えます。
/pkg/rust2wasm_bg.wasm
というファイルが、RustからコンパイルされたWebAssemblyファイルで、これらをjsでラップしているファイルがpkg/rust2wasm.js
です。
// 省略
export function greet() {
wasm.greet();
}
jsのgreetという関数が、wasm(WebAssembly)へ変換された元Rustのgreetを呼び出していますね。
ブラウザ上で動かしてみる
本記事ではnpmでwebプロジェクトを生成します。
create-wasm-appというWebAssemblyをブラウザ上で動かすための雛形プロジェクトを利用しました。
プロジェクト内で以下を実行します。appは生成ターゲットのディレクトリなので、適宜変えることが可能です。
$ npm init wasm-app app
appディレクトリに入って、パッケージのインストールをします。
$ cd app
$ npm install
app/index.htmlから、app/bootstrap.jsを介して、app/index.jsをインポートする仕組みなっています。
import * as wasm from "hello-wasm-pack";
wasm.greet();
hello-wasm-pack
はnpm install
でインストールされるパッケージで、WebAssemblyファイルと、それのラッパーであるjsファイルが配置されています。
つまりデフォルトのindex.htmlでは、hello-wasm-pack
にあるhello_wasm_pack_bg.wasm
を呼び出しているということです。npm run start
をしてみると、以下のように表示されます。
これをRust側の関数で定義した、"Hello, rust2wasm!"
が表示されるようにしたい、というのが目標です。
方針として、Rustから生成したwasmファイルをローカル内でリンクさせて、そこから呼び出すという形を取ります。
まず、pkg
ディレクトリ内で
$ npm link
を実行します。
その後、app
ディレクトリで、リンクしたパッケージを利用できるようにします。
$ npm link rust2wasm
これで、ローカルに存在するnpmパッケージを、app
にあるプロジェクト内で利用できるようになります。
最後にindex.jsを書き換えます。
import * as wasm from "rust2wasm";
wasm.greet();
ここまでやると、
ということで、Rustで書かれた関数をjsを介して呼び出すことができました。
Rustの関数を書き換える -> wasm-pack build
というサイクルを回すことで開発を進めていくことができます。
まとめ
RustからWebAssemblyにコンパイルして、それをラップしたjsを介して呼び出すことで、Rustで書かれたコードをブラウザ上で動かすことができました。
コンパイルが若干煩雑なところがありますが、現存している言語資産を活用しつつ、js以外でフロント側の開発ができる可能性を秘めているという点で、とても面白みがあって、今後の発展を期待したい技術だと思っています。