PR

Rust から WebAssembly を出力してみる

1. はじめに

Rust から WebAssembly を出力してみます。
WebAssembly は、ブラウザ上で動作するバイナリフォーマットで、C/C++ や Rust などの言語からコンパイルされます。これにより、Web アプリケーションのパフォーマンスを向上させることができます。

割と昔からある技術のようですが、WebAssembly を使ったことがなかったのと、Rust も最近始めたので試してみることにしました。

2. Rust にパッケージ追加

Rust に WebAssembly を出力するためのパッケージを追加します。

rustup target add wasm32-unknown-unknown

wasm32-unknown-unknown は、WebAssembly のいくつかあるターゲットアーキテクチャの一つのようです。
ターゲットプラットフォームが unknown ということで、特定の OS や CPU アーキテクチャに依存しないことを意味しています。
一番汎用的そうですが、替わりに標準的なライブラリは利用できないようです。

3. プロジェクト作成とビルド

JavaScript から呼び出すため --lib で作成

cargo new --lib rust_wasm
cd rust_wasm

Cargo.toml に以下を追加

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

src/lib.rs に以下を追加

#[unsafe(no_mangle)]
pub fn main() -> i32 {
    println!("Hello, world!");
    return 0;
}

GitHub Copilot が書いてくれました。
#[no_mangle] は、Rust のコンパイラに対して、関数名を変更しないように指示する属性です。
これがないと、Rust のコンパイラは関数名を変更してしまい、JavaScript から呼び出せなくなります。
本来は #[no_mangle] だけで良いらしいのですが、unsafe をつけないとコンパイルエラーになりました。
unsafe は、Rust の安全性を無視することを示すキーワードで、通常は避けるべきですが、ここでは WebAssembly に出力するために必要なようです。

ビルド

cargo build --target wasm32-unknown-unknown

これで、target/wasm32-unknown-unknown/debug/rust_wasm.wasm が生成されます。

index.html を作成

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WASM Example</title>
</head>
<body>
    <h1>WASM Example</h1>
    <script>
        async function loadWasm() {
            try {
                const response = await fetch('rust_wasm.wasm');
                const bytes = await response.arrayBuffer();
                const { instance, module } = await WebAssembly.instantiate(bytes);
                // エクスポートされた関数名を一覧表示
                console.log("Exported functions:", Object.keys(instance.exports));

                console.log('WASM Loaded:', instance.exports.main());
                // ...必要に応じてWASMインスタンスを使用するコードを追加...
            } catch (error) {
                console.error('Error loading WASM:', error);
            }
        }

        loadWasm();
    </script>
</body>
</html>

動いている Web サーバーに、rust_wasm.wasm を配置して、index.html をブラウザで開くと、コンソールにエクスポートされた関数名と WASM Loaded: 0 が出力されます。
println! は、WebAssembly では動作しないので、コンソールに表示されません。
一先ずこれで動作は確認できました。

4. Docker で動かす

出来上がった WASM を毎回 Web サーバーに配置するのは面倒なので、Docker で動かしてみます。

4.1 Dockerfile の準備

FROM node:alpine
RUN npm install -g http-server
WORKDIR /usr/src/app
EXPOSE 8080
CMD ["http-server", "-p", "8080"]

4.2 フォルダ構成の例

index.html と Dockerfile を rust_wasm.wasm が出力される target/wasm32-unknown-unknown/debug に配置します。

rust_wsam/
└── target
    └── wasm32-unknown-unknown
        └── debug
            ├── Dockerfile
            ├── index.html
            └── rust_wasm.wasm

4.3 イメージをビルド

ターミナル(PowerShell や WSL など)を開いて、プロジェクトフォルダに移動して以下のコマンドを実行します。

docker build -t my-http-server .

my-http-server は好きな名前で OK。
. は現在のディレクトリをコンテキストとして指定。

4.4 ボリューム付きコンテナを起動

docker run -d -p 8080:8080 -v ${PWD}:/usr/src/app my-http-server

4.5 ブラウザで確認

ブラウザで http://localhost:8080 を開くと、コンソールにエクスポートされた関数名と WASM Loaded: 0 が出力されます。
これでビルドし直してもブラウザをリロードするだけで、WASM を確認できるようになりました。

5. まとめ

Rust から WebAssembly を出力し、Docker のコンテナから確認するところまでやってみました。
WebAssembly を使う準備はできたので、次は実際に WebAssembly を使って何か作ってみたいと思います。

今回の記事も、Github Copilot が7割くらい書いてくれました。

A. 参考サイト

WebAssembly - Rustプログラミング言語
Rust での非 Web 向け Wasm モジュール作成
Rust で WebAssembly を出力する
5 分で WebAssembly + Docker = Hello World

B. 関連書籍

コメント