【入門】Rust + WebAssembly で hello world をしてみた

この記事は何

この記事は、Rust + WebAssembly で hello, wolrd してみたというものです。 筆者は Rust、WebAssembly ともに全く触ったことがなく環境構築すらしたことがないため、ちゃんとした解説記事ではありません。 ざっくりと作業の流れをメモ程度に書き残したものです。 同じように Rust + WebAssembly で hello, world する人の一助になれば良いなと思っています。

基本的には下記のリンクに書かれている通りに進めましたが、一部で違うことをやっています。

developer.mozilla.org

対象読者

  • shell 上の操作がある程度できる。
  • Nodejs や npm (yarn) などの環境がある。
  • Unix 環境で作業している。

WebAssembly とは?

MDN に以下のようにまとまっています。

WebAssembly は最近のウェブブラウザーで動作し、新たな機能と大幅なパフォーマンス向上を提供する新しい種類のコードです。基本的に直接記述ではなく、C、C++、Rust 等の低水準の言語にとって効果的なコンパイル対象となるように設計されています。 この機能はウェブプラットフォームにとって大きな意味を持ちます。 — ウェブ上で動作するクライアントアプリで従来は実現できなかった、ネイティブ水準の速度で複数の言語で記述されたコードをウェブ上で動作させる方法を提供します。

ざっくり言えば、C や C++ などで記述された比較的低水準にリソースを利用するバイナリを、ブラウザ(や Nodejs、Deno)などの Javascript 実行環境から利用できるというものです(たぶん)。

パフォーマンスをごりごりにチューニングした重い処理を書けたり、C などにしかない機能を使えたりしそうですね。

hello, world

環境構築

Rust のインストール

Install Rust - Rust Programming Language ここにしたがってインストールするだけです。 インストールされたかどうかは、rustup --version でチェックできます。

WebAssembly のビルド

cargo という rust におけるパッケージ管理ツールがあります。 まずは、wasm-pack なる WebAssembly 向けにコンパイルをするためのパッケージをインストールします。

$ cargo install wasm-pack

次に、cargo コマンドでプロジェクトディレクトリを作ります。 まず、適当な作業ディレクトリを作成してください。その中で、以下のコマンドを叩きます。

$ cargo new --lib hello-wasm

こんな感じのディレクトリ構成を持つディレクトリができるはずです。

+-- Cargo.toml
+-- src
    +-- lib.rs

lib.rs にはすでにサンプルコードが吐き出されているはずです。 このサンプルコードを消して、代わりに以下のコードを貼り付けます。

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));
}

それぞれの記述の解説は、Rust から WebAssembly にコンパイルする - WebAssembly | MDN に譲ります。

次に、cargo.toml を修正します。これは、npm における package.json のようなものです。

[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"

[package] の部分は、npm に公開するときの情報として参照されるだけなので、重要なのは [lib][dependencies] だけです。

さて、準備が整ったのでビルドしてみましょう。

$ wasm-pack build --scope mynpmusername

これでビルドが走ります。簡単ですね。 MDN のチュートリアルでは、これでビルドしたパッケージを npm に公開しているのですが、ここでは公開せずに手元で使うことを考えます。

WebAssembly を利用する

作業ディレクトリで準備します。

$ yarn init
$ yarn add -D webpack webpack-cli webpack-dev-server
$ yarn install

今回はブラウザで WebAssembly を試したいので、webpack の dev-server を使います。 ちなみに、上のコマンドを叩いて webpack 等を入れると、チュートリアルの webpack とバージョンが違います。 そのため、webpack.config.js にちょっとした修正が必要になります。

// webpack.config.js

const path = require('path');

module.exports = {
    entry: './index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'index.js',
    },
    mode: 'development',
    experiments: {
        syncWebAssembly: true,
    },
};

experiments: { syncWebAssembly: true } のオプションを追加しましょう。*1

では、HTML と JS ファイルを用意します。

// index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>hello-wasm example</title>
  </head>
  <body>
    <script src="./index.js"></script>
  </body>
</html>
// index.js

const js = import('./hello-wasm/pkg/hello_wasm.js');

js.then((js) => {
    js.greet('World');
});

これで準備は完了です。hello world を表示しましょう。

$ yarn webpack-cli serve

ブラウザからアクセスすると、アラートが表示されるはずです。

まとめ

今回は、Rust + WebAssembly をゼロから hello world することろまで書きました。 Rust で書いた処理が Javascript から参照できて。しかも Javascript から渡した引数も参照できるのは何だか不思議な感じがしますね。

まだ全然分からないことばかりなので、WebAssembly の実際のユースケースとかあれば是非教えてください。

*1:syncWebAssembly オプションは wasm の読み込み方法を指定しているが、実は Webpack4 以前の方法であり depricated。本来は、asyncWebAssembly オプションを使うべき。