RustでWebAssembly入門のためのまとめ

RustでWebAssembly入門のためのまとめ

[20190201_RustでWebAssembly入門のためのまとめ

Rust で WebAssembly に入門する

Introduction – Rust and WebAssembly

上記ページが Rust + WebAssembly のためのチュートリアル & リファレンスのページになります。このページを参考に、Rust のコードから WebAssembly を生成するための環境構築~サンプルコードの実装までを試します。

WebAssembly とは

WebAssembly とは、ブラウザ上で動作するプログラミング言語の1種です。Javascriptと違いバイナリ形式での実行が可能であるため、実行速度がJavascriptに比べて向上します。

主要ブラウザ (Firefox, Chromium, Google Chrome, Microsoft Edge) 上で動作がサポートされています。IEは未対応です。

WebAssembly のコード(wasm) は、C, C++ 等のプログラミング言語からコンパイルして生成することになります。ほかにも Rust, Go などの言語でも開発できます。

Rust + WebAssembly セットアップ

今回は Rust をコンパイルして WebAssembly を生成します。Windows環境での動作を想定しています。Rust で WebAssembly を開発するためには以下のツールが必要です。

Rust Toolchain

Install – Rust programming language

rustup, rustc, cargo のことです。上記ページから環境に合わせてインストールしてください。それぞれのコマンドが使えるようになればOKです。

後述の wasm-pack を使うために Rust 1.30.0以降が必要 になります。

Rustインストールから入門する

上記ページで一通りインストールからセットアップを行っていますので参考にしてください。

wasm-pack

wasm-pack

Rust で WebAssembly を構築したり、テストや公開するためのコマンドが諸々入ったツールです。上記ページから環境に合わせてインストールしてください。

$ cargo install wasm-pack

インストールはすぐに完了します。設定等もありません。

cargo-generate

GitHub – ashleygwilliams/cargo-generate: cargo, make me a project cargo-generate は、指定されたレポジトリをもとに、テンプレートプロジェクトを生成してくれる便利ツールです。cargo でインストールして使えるようにしておきます。

$cargo install cargo-generate

これを使って環境を作ります。

npm

Node.js

最後に npm を使えるようにしてください。Node.js をインストールすると使えるようになります。あたらしくNode.js をインストールする場合は最新の安定版を入れて、最新のnpmが使えるようにします。

すでにnpmが使える場合は最新版にしておきます。npm

$ npm install npm@latest -g

Hello World サンプル

セットアップが完了したら、サンプルプログラムを作って動かしてみます。”Hello World” をアラートするだけの簡単なものです。

Rustプロジェクトテンプレートを複製する

GitHub – rustwasm/wasm-pack-template: a template for starting a rust-wasm project to be used with wasm-pack

このプロジェクトは、Rustで作られたライブラリコードを WebAssembly にコンパイルし、npm に公開するためのものです。

以下のコマンドでテンプレートを複製し、プロジェクトを作成します。パラメータ(--name my-project)でプロジェクト名を指定しています。指定したプロジェクト名のフォルダ以下にテンプレートが生成されます。

$ cargo generate --git https://github.com/rustwasm/wasm-pack-template --name my-project
$ cd my-project

中身は以下のような構成になっています。

my-project/
├── Cargo.toml
├── LICENSE_APACHE
├── LICENSE_MIT
├── README.md
└── src
    ├── lib.rs
    └── utils.rs

src/lib.rs がプログラムのルートです。以下のような内容になっています。

extern crate cfg_if;
extern crate wasm_bindgen;

mod utils;

use cfg_if::cfg_if;
use wasm_bindgen::prelude::*;

cfg_if! {
    // When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
    // allocator.
    if #[cfg(feature = "wee_alloc")] {
        extern crate 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, my-project!");
}

Javascriptの alert() をインポートしてRustから呼び出せるようにしています。また、Rustで定義した greet() をJavascript側から利用できるようにしています。

src/utils.rs は、デバッグ用の何からしいです。

コンパイルしてwasmファイルを生成する

$ wasm-pack build

wasm-pack コマンドでビルドすると pkg ディレクトリが生成され、その中に次のようなファイルが生成されます。

pkg/
├── package.json
├── README.md
├── my_project_bg.wasm
├── my_project.d.ts
└── my_project.js

my_project_bg.wasm ファイルが、Rustコードをコンパイルすることによって得られたWebAssemblyバイナリです。この中にある処理を呼び出すためのラッパーが、ts, jsのファイルです。Typescriptの環境下で使用するには ts ファイルを使えばいいということでしょう。

/* tslint:disable */
import * as wasm from './my_project_bg';

let cachedTextDecoder = new TextDecoder('utf-8');

let cachegetUint8Memory = null;
function getUint8Memory() {
    if (cachegetUint8Memory === null || cachegetUint8Memory.buffer !== wasm.memory.buffer) {
        cachegetUint8Memory = new Uint8Array(wasm.memory.buffer);
    }
    return cachegetUint8Memory;
}

function getStringFromWasm(ptr, len) {
    return cachedTextDecoder.decode(getUint8Memory().subarray(ptr, ptr + len));
}

export function __wbg_alert_6c792a5e55b14999(arg0, arg1) {
    let varg0 = getStringFromWasm(arg0, arg1);
    alert(varg0);
}
/**
* @returns {void}
*/
export function greet() {
    return wasm.greet();
}

wasmファイルをインポートして、そこで定義されている greet() を呼び出しています。これは Rust で定義した処理です。

package.jsonファイルには、生成されたコードのメタ情報が記述されており、このパッケージをNPMで公開するときに役立ちます。

Webプロジェクトを生成する

cargo で生成したプロジェクト内に、Webページ用のフォルダを生成し、wasmを実行できるようなプロジェクトを作ります。上で生成したjsファイルを使って手動で構築もできそうですが、wasm用のプロジェクトを生成するための便利ツールがあるので、これを利用します。

$ npm init wasm-app www

このコマンドを実行するためには新しめの npm が必要になるようです。

実行すると、プロジェクトディレクトリ内に /www ディレクトリが生成され、そこに必要なファイル等が自動で生成されます。

my-project/www/
├── bootstrap.js
├── index.html
├── index.js
├── LICENSE-APACHE
├── LICENSE-MIT
├── package.json
├── README.md
└── webpack.config.js

webpackを使っていますが、必要な設定は完了しているので特にファイルを触る必要はありません。

必要な依存パッケージをインストールするために www で install コマンドを実行します。

$ cd www
$ npm install

ローカルのパッケージリンクする

先ほど wasm-pack コマンドで生成した wasm ファイルをローカルパッケージとしてリンクします。npm link コマンドは npm にパッケージを公開していないローカルパッケージを、npm プロジェクトから利用できるようにするものです。

まずは、/pkg 内でリンクコマンドを実行します。

$ npm link

次に、/www で、リンクしたパッケージを利用できるようにします。

$ npm link my-project

パッケージ名は /www/package.json の name に書かれている値です。

これで利用する準備は完了です。

ローカルで動かしてみる

wwwディレクトリの中身ですが、index.html が bootstrap.js を経由して index.js を読み込んでいます。index.js の内容を先ほどリンクした内容に書き換えます。

import * as wasm from "hello-wasm-pack";

wasm.greet();

では実行します。

ローカルに開発用サーバーを立てて動かすコマンドが用意されているので利用します。

$ npm run start

これでサーバーの起動完了です。localhost:8080 にアクセスすると、index.html の内容が表示され、alert が呼ばれます。

wasm-pack build で Rust を再コンパイルすると、wasm ファイルが更新され、ブラウザがリロードされます。

あとは、開発するだけです。

以上。

Rustカテゴリの最新記事