飴屋

WebAssembly/RustでWebAssemblyを始める

wasm-pack

RustでWebAssemblyを始めるにはwasm-packというツールを使え、とのことでした。インストールは以下のコマンド。

cargo install wasm-pack

WebAssemblyのコードにコンパイルしたり、Node.jsのパッケージマネージャー(npm)向けにパッケージンググしてくれたりするようです。Node.jsで何かする予定は今のところないですが、MDNの初学者向けのコンテンツがNode.jsのパッケージを作る構成になっていたので、それに沿って進めてみます。

cargo-generate

cargo install cargo-generate

↑このコマンドでcargo-generateをインストールしておくとRustプロジェクト立ち上げのときにテンプレートとして便利なんだそうです。とりあえず入れてみたらなんかいろいろインストールされましたが、今のところ詳細は不明です。

Node.js

別にNode.jsを使わなくても目的は果たせそうにもみえますが、人にものを教わるときは素直に言うことを聞く私ですので、npm用のパッケージ制作にまず挑戦するのでした。そのためにNode.jsをインストールしておきます。というか既に入っていました。最近(執筆時2020年)はチーム組んでWeb制作のお仕事をするとき、必ずしもNode.jsを使うというわけでもないですが、やはりよく利用されるイメージはあります。今回、WebAssemblyと協調してJavaScriptも動くことでしょうし、Node.jsを採用するメリットもあるのかもしれません。

cargo new --lib

 --libを引数につけるとライブラリを新規作成してくれるとのこと。

cargo new --lib hello-wasm

言われるがまま、ライブラリのプロジェクトを作成してみます。src/lib.rsにはこんな雛形ができていました。

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}

なんだろう。2+2=4を確認するテストコードかな、とか中身を吟味しようと思ったら不要なので全部消せとのことでした。代わりに書いたのがこちら↓。

extern crate wasm_bindgen;

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
extern {
    pub fn alert(s: &str);
}

#[wasm_bindgen]
pub fn greet(name: &str) {
    alert(&format!("Hello, {}!", name));
}

wasm_bindgenというクレートを使うという宣言と、その中のprelude(前奏・序曲の意かな?)の中のクラスをインポート(use)するという宣言が出てきました?preludeってなんだろうと思って調べたら、Rustにはよく使われるクラスを簡便にまとめるためにpreludeとしてまとめる文化があるようでした。いつか自分もクレートを設計したら使ってみたい。

wasm-bindgenクレートはJavaScriptとRustをつないでくれる橋渡し役だそうで、両者で異なる変数の型情報を整理してくれたり、JavaScript側で発生した例外をRust側でキャッチしてくれたりするとありました。絶対必要だけど自分で実装することでもなさそうな部分が事前に用意してもらっていると考えると頼もしいですね。

#[wasm_bindgen]はアトリビュートで次の行を修飾する行です。Javaでいうアノテーション、他にもデコレーターとか言語によって呼び名が違いますが、アトリビュートだとHTMLのAttributeと言葉が被ってややこしくならないのかな、とか心配してみたり。このアトリビュートをつけておくと明示的にwasm_bindgenが次の関数を作用の対象に入れてくれるようです。

externされているalert関数はJavaScriptのalert命令のことですね。こうしてシグニチャを用意しておくと、Rustがわからalert命令を呼べるんですね。張り子の虎に過ぎないので関数の実体はここで実装する必要はないのです。

続いて、やはり#[wasm_bindgen]アトリビュートがついているgreet関数が登場しました。渡された名前の文字列に「Hello, !」をくっつけて、先ほど定義したalert関数経由でJavaScripのalert文を呼び出す、という内容のようです。この関数はwasm_bindgenによって、JavaSxriptから呼び出せるようになるみたいです。

JavaScripとRustのキャッチボールが既に実装されたので、早くビルドして試してみたい!ってなりますね。

Cargo.toml

でも、その前にプロジェクト設定ファイルのCargo.tomlを更新する必要があるそうです。

[package]
name = "hello-wasm"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]
description = "A sample project with wasm-pack"
license = "MIT/Apache-2.0"
repository = "https://github.com/yourgithubusername/hello-wasm"

[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "0.2"

ライセンス情報とかリポジトリ情報とか書いてありましたが、公開するわけでもないのでいったん無視し、[lib][dependencies]の項を書き足しました。
[dependencies]の項はクレートの依存関係を書くんだろうな、となんとなくわかるのですが[lib]項のcrate-typeってなんでしょうね?調べたところによると cdylib は「Rust用ではない(c)」「動的な(dy)」ライブラリ(lib)を意味するようです。wasmファイルとして動的にJavaScriptから呼び出されるのだから、なるほどcdylib指定に合点がいきました。ただライブラリをクレートと言い張る割に「lib」って言葉がよく出てきますよね。使い分ける法則があるのか謎です。

wasm-pack build

準備が整ったのでビルドしてみます。コマンドはCargo.tomlのあるディレクトリで↓このコマンドです。

wasm-pack build --scope mynpmusername

できた!と思ってtargetディレクトリを覗いてみましたが、wasmファイルこそできているものの何が何のためのファイルだかわからない状況でした。それもそのはず、wasm-packではさらに進んでpkgディレクトリにコピーしたwasmファイルに対して

  1. wasm-bindgenでJavaScript向けにwasmファイルのラッパーを作成
  2. Cargo.toml同等のpackage.jsonを作成

などをやってくれるそうです。つまりpkgディレクトリの中身がnpmパッケージになっているということのようです。まぁパッケージされても配布しないので、今は無視して、wasmファイルとラッパーのjsファイルだけもらっておきましょう。

テスト

適当なWEB環境を用意して、適当なHTMLファイルを用意して、

const js = import("./node_modules/@yournpmusername/hello-wasm/hello_wasm.js");
js.then(js => {
  js.greet("WebAssembly");
});

↑こんな風にスクリプトを読み込ませて動作確認したところ、んん?エラーを吐いてダメだった!?

次回、「WebAssemblyをブラウザで動かす」へ続く。