Contents
環境構築:rustup と wasm ターゲットのセットアップ
Rust で WebAssembly を扱う第一歩は、安定版コンパイラと wasm32-unknown-unknown ターゲットを正しく導入することです。これが整っていれば、その後に続くツール(wasm‑pack・wasm‑bindgen など)はすべて同一環境で動作し、ビルドエラーの原因を大幅に減らせます。本節では OS を問わず共通になるインストール手順と、導入後にバージョンやターゲットが正しく設定されたことを確認する方法を解説します。
rustup のインストールと stable ツールチェーンの取得
rustup は公式が提供するツールチェーン管理ユーティリティで、OS に依存せずに最新の stable コンパイラを入手できます。
- macOS / Linux(シェル)
bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source "$HOME/.cargo/env"
rustup install stable
rustup default stable
- Windows(PowerShell)
powershell
iwr https://win.rustup.rs -UseBasicParsing | iex
rustup install stable
rustup default stable
インストールが完了したら、次のコマンドでバージョン情報を確認します。
|
1 2 |
rustc --version # 例: rustc 1.78.0 (2024‑03‑15) |
実際の日付は環境に応じて変わりますが、stable が表示されれば準備完了です。
wasm32-unknown-unknown ターゲットの追加
WebAssembly 用のコンパイルターゲットは以下の一行でインストールできます。
|
1 2 |
rustup target add wasm32-unknown-unknown |
このターゲットはブラウザ実行を前提とした最小ランタイムで、余計なシステム依存が除外されるためバイナリサイズが小さくなります。追加後は次のコマンドでビルドが可能か確認してください。
|
1 2 |
cargo build --target wasm32-unknown-unknown |
target/wasm32-unknown-unknown/debug/*.wasm が生成されていれば、ターゲットは正しく設定されています。Windows 環境でも Visual Studio Build Tools は不要で、Rust のみで完結します。
プロジェクト作成とビルド:wasm‑pack とテンプレート活用
本節では最新の wasm-pack(0.12 系)と公式テンプレートを組み合わせて、ベストプラクティスがすぐに使えるプロジェクト構造を作る手順を紹介します。CI にも組み込みやすい形で説明するので、実務への導入が容易です。
wasm‑pack と cargo‑generate のインストール
wasm-pack は Cargo 経由でも簡単にインストールできますが、バージョン指定の書式は --version オプションを使う方が Cargo の実装と合致します。
|
1 2 3 |
cargo install wasm-pack --version "^0.12" cargo install cargo-generate |
インストール後は次で確認しましょう。
|
1 2 3 |
wasm-pack --version # 例: wasm-pack 0.12.3 cargo generate --version |
Linux/macOS では $HOME/.cargo/bin が PATH に入っていることを忘れずにチェックしてください。
テンプレートからプロジェクトを生成
公式テンプレート rust-wasm-template は wasm-bindgen = "0.2" と web-sys の推奨依存関係がすでに組み込まれており、手作業での設定ミスを防げます。以下は生成からビルドまでの流れです。
|
1 2 3 4 |
cargo generate --git https://github.com/rustwasm/rust-wasm-template --name my_wasm_app cd my_wasm_app wasm-pack build --target web # デバッグビルドなら --dev を付与 |
ビルドが成功すると pkg/ 以下に次のようなファイルが出力されます。
my_wasm_app_bg.wasm– 実体バイナリmy_wasm_app.js– JavaScript ラッパー
CI での自動テスト例(GitHub Actions)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
name: Wasm CI on: push: branches: [ main ] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install Rust toolchain run: rustup default stable - name: Install wasm-pack & cargo-generate run: | cargo install wasm-pack --version "^0.12" cargo install cargo-generate - name: Build and test run: | cd my_wasm_app wasm-pack test --headless --firefox |
この設定だけでプルリクエストごとにブラウザ上のテストが走り、品質を保てます。
Rust と JavaScript の相互運用:wasm‑bindgen と web‑sys の実践例
WebAssembly からブラウザ API を呼び出す際の主力ツールは wasm-bindgen と web-sys です。ここでは文字列・配列のやり取りと、DOM 操作・イベントハンドラ登録の具体コードを示しながら、型変換のポイントを解説します。
文字列・バイナリの受け渡し
wasm-bindgen が提供する Uint8Array や JsValue を活用すれば、ポインタ演算を書かずに安全にデータ転送できます。以下は Rust 側と JavaScript 側のサンプルです。
Rust(src/lib.rs)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
use wasm_bindgen::prelude::*; use js_sys::{Uint8Array}; #[wasm_bindgen] pub fn reverse_bytes(input: Uint8Array) -> Uint8Array { // Uint8Array → Vec<u8> に変換し、内部で逆順にするだけ let mut vec = input.to_vec(); vec.reverse(); Uint8Array::from(&vec[..]) } #[wasm_bindgen] pub fn greet(name: &str) -> String { format!("Hello, {}!", name) } |
JavaScript(src/index.js)
|
1 2 3 4 5 6 7 8 9 10 11 12 |
import init, { reverse_bytes, greet } from "./pkg/my_wasm_app.js"; async function run() { await init(); // wasm モジュールの初期化 console.log(greet("Rust")); // → "Hello, Rust!" const data = new Uint8Array([1,2,3,4]); const rev = reverse_bytes(data); console.log(rev); // Uint8Array [4,3,2,1] } run(); |
ポイントは Uint8Array::from(&vec[..]) が内部でコピーを行い、元の Vec<u8> は安全に破棄できる点です。
DOM 操作とイベントハンドラ登録
web-sys の型定義と Closure::wrap を組み合わせれば、JavaScript 側のコールバックを Rust で記述できます。以下はボタンのクリックをコンソールに出す例です。
Rust(src/lib.rs)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
use wasm_bindgen::prelude::*; use web_sys::{window, HtmlElement}; use wasm_bindgen::JsCast; #[wasm_bindgen(start)] pub fn start() -> Result<(), JsValue> { // ドキュメントとボタン要素を取得 let document = window().unwrap().document().unwrap(); let btn = document .get_element_by_id("myBtn") .expect("button not found") .dyn_into::<HtmlElement>()?; // クロージャの作成 let closure = Closure::wrap(Box::new(move || { web_sys::console::log_1(&"Button clicked!".into()); }) as Box<dyn FnMut()>); // onclick に設定し、JS 側で保持させる btn.set_onclick(Some(closure.as_ref().unchecked_ref())); closure.forget(); // メモリリーク防止のために JS が所有 Ok(()) } |
HTML(public/index.html)
|
1 2 3 4 5 6 7 8 9 |
<!doctype html> <html lang="ja"> <head><meta charset="utf-8"><title>Wasm Demo</title></head> <body> <button id="myBtn">Click me</button> <script type="module" src="./pkg/my_wasm_app.js"></script> </body> </html> |
Closure::wrap(...).forget() が必要なのは、JavaScript の GC がクロージャを解放しないように参照を保持させるためです。これが抜けているとクリック時にパニックが発生します。
WebAssembly Component Model 入門と Rust 側実装
Component Model は大規模サービスで「モジュールの再利用」と「言語間相互運用」を実現する新しいアプローチです。Rust エコシステムは 2025 年以降、cargo-component と wit-bindgen による公式サポートが整備されました。本節では WIT ファイルの記述からコンポーネントビルド、Node.js/Wasmtime での実行例までを順に追います。
WIT(WebAssembly Interface Types)によるインターフェース定義
WIT は言語非依存の IDL であり、wit-bindgen が自動的にシリアライズコードを生成します。以下は簡単な電卓インターフェースです。
example.wit
|
1 2 3 4 5 6 7 |
package example:calc interface Calculator { add: func(a: s32, b: s32) -> s32; mul: func(a: s32, b: s32) -> s32; } |
Rust 側の実装とコード生成
cargo component new で作成したプロジェクトに example.wit を配置すると、ビルド時に自動でバインディングが生成されます。
src/lib.rs
|
1 2 3 4 5 6 7 8 |
wit_bindgen::generate!("example.wit"); // 実装は自動生成されたトレイトを実装するだけ impl Calculator for Component { fn add(&self, a: i32, b: i32) -> i32 { a + b } fn mul(&self, a: i32, b: i32) -> i32 { a * b } } |
cargo component build --release を実行すると、Component Model 用の .component.wasm が target/wasm32-wasi/release/ に出力されます。
Node.js(Wasmtime)でのロード例
|
1 2 3 4 5 6 7 8 9 10 11 |
// node >= 18 が必要です (ESM 対応) import { readFile } from "node:fs/promises"; import { instantiate } from "@bytecodealliance/preview2-shim"; (async () => { const wasmBytes = await readFile("./target/wasm32-wasi/release/example.component.wasm"); const { instance } = await instantiate(wasmBytes); console.log("3 + 4 =", instance.exports.add(3, 4)); // → 7 console.log("5 * 6 =", instance.exports.mul(5, 6)); // → 30 })(); |
ポイントは preview2 がデフォルトで有効になるため、従来の WASI (wasi_snapshot_preview1) 用コードを書き換える必要がない点です。将来的に wasmtime の新バージョンがリリースされても互換性が保たれます。
バンドラ統合とデバッグ設定:Vite/esbuild と最適化テクニック
フロントエンドビルドツールは Wasm のロード速度や開発時のデバッグ体験に大きく影響します。2026 年版 Vite と esbuild は source‑map の自動生成と Wasm のインライン読み込みを標準サポートしています。また、Rust 側で console_error_panic_hook を有効化すれば panic 時のスタックトレースがコンソールに出力され、原因特定が容易になります。
console_error_panic_hook の組み込み
wasm-pack でビルドした Wasm がブラウザ上で実行されるとき、デフォルトでは panic が単なるエラーコードになるため、開発中に情報が失われます。以下のように #[wasm_bindgen(start)] に組み込むだけで、可読なスタックトレースを得られます。
|
1 2 3 4 5 6 7 8 9 |
use wasm_bindgen::prelude::*; use console_error_panic_hook; #[wasm_bindgen(start)] pub fn init() { // 1回だけ設定すれば OK console_error_panic_hook::set_once(); } |
注意点
- 本番ビルドでは panic = "abort" が推奨されます。Cargo.toml の [profile.release] に panic = "unwind" を明示的に書かない限り、上記フックは実行されません。
source‑map 有効化とブラウザデバッグ
Vite での設定は非常にシンプルです。vite.config.js に build.sourcemap: true を入れ、wasm-pack build --dev と組み合わせるだけで Chrome DevTools から Rust ソースを直接ステップ実行できます。
|
1 2 3 4 5 6 7 8 9 10 |
// vite.config.js import { defineConfig } from "vite"; export default defineConfig({ build: { target: "es2022", sourcemap: true, // ← source‑map を出力 }, }); |
ビルドコマンド例:
|
1 2 3 |
wasm-pack build --dev --target web # デバッグ情報を保持したままビルド npm run dev # Vite が .wasm と .map を提供 |
非同期呼び出しとメモリプールのベストプラクティス
wasm-bindgen-futures と js_sys::Promise を併用すれば、Rust の async fn が JavaScript の非同期 API(fetch, IndexedDB など)と自然に連携します。以下は HTTP GET の結果を文字列として返す例です。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
use wasm_bindgen::prelude::*; use wasm_bindgen_futures::JsFuture; use web_sys::{Response}; #[wasm_bindgen] pub async fn fetch_text(url: String) -> Result<JsValue, JsValue> { // 1. JavaScript の fetch を呼び出す let resp_value = JsFuture::from(web_sys::window() .unwrap() .fetch_with_str(&url)) .await?; // 2. Response オブジェクトへ変換 let resp: Response = resp_value.dyn_into()?; // 3. text() メソッドは Promise を返すので await let txt = JsFuture::from(resp.text()?).await?; Ok(txt) } |
最適化のヒント
- wasm-opt -Oz(サイズ最小化)でビルドした後でも、async fn は正しくインラインされ、実行時オーバーヘッドはほぼゼロです。
- 大規模データを扱う場合は MemoryPool パターン(事前確保バッファプール)と組み合わせることで GC の回数を減らせます。
デプロイと本番運用:Edge 環境への配置とセキュリティ対策
WebAssembly の軽量性はエッジプラットフォームで最大限に活かせます。2025 年以降、Cloudflare Workers と Netlify Edge Functions は WASI‑preview2 に対応し、サンドボックスが強化されたことで安全かつ高速なデプロイが可能となりました。本節ではそれぞれの最新ランタイム仕様に合わせたコード例と、運用時に気を付けるべきセキュリティポイントをまとめます。
Cloudflare Workers へのデプロイ(モジュールスクリプト)
Workers の最近の推奨スタイルは ESM モジュールです。wrangler が生成するテンプレートに手を加えて、Wasm バイナリを直接インポートします。
|
1 2 3 4 |
npm install -g @cloudflare/wrangler wrangler login wrangler init my-wasm-worker --type=javascript # テンプレート作成 |
src/index.js の内容例:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// ESモジュールとして Wasm をインポート import wasmUrl from "./pkg/my_wasm_app_bg.wasm"; addEventListener("fetch", event => { event.respondWith(handleRequest(event.request)); }); async function handleRequest(request) { // WebAssembly.instantiateStreaming が Workers でも利用可能 const { instance } = await WebAssembly.instantiateStreaming(fetch(wasmUrl), {}); // greet は Rust 側でエクスポートされた関数と仮定 const resultPtr = instance.exports.greet("Cloudflare"); // greet が文字列ポインタを返す場合は js-sys の helper が必要ですが… return new Response(`Result: ${resultPtr}`); } |
バイナリのバージョン管理
Workers KV を使うと再デプロイなしで Wasm 本体だけ差し替えられます。
|
1 2 |
wrangler kv:key put WASM_BINARY "$(cat ./pkg/my_wasm_app_bg.wasm | base64)" |
コード側では KV から取得したバイト列を WebAssembly.instantiate に渡すだけです。これにより CI のビルドとデプロイが分離され、ロールバックも容易になります。
Netlify Edge Functions と WASI‑preview2
Netlify は Edge Runtime に wasmtime(preview2 対応)を組み込んでいます。そのため fs::read_to_string のような標準的な Rust I/O がサンドボックス内で利用可能です。以下は Edge Function から Wasm をロードし、同様に greet 関数を呼び出す例です。
netlify.toml
|
1 2 3 4 5 6 7 8 |
[build] command = "npm run build" publish = "dist" [[edge_functions]] path = "/wasm/*" function = "wasm-handler" |
functions/wasm-handler.js
|
1 2 3 4 5 6 7 8 9 |
import wasmUrl from "./pkg/my_wasm_app_bg.wasm"; export default async (request, context) => { // Netlify Edge は fetch が利用できるので streaming でロード const { instance } = await WebAssembly.instantiateStreaming(fetch(wasmUrl), {}); const greeting = instance.exports.greet("Netlify"); return new Response(`🦀 ${greeting}`); }; |
セキュリティとリソース制限
- メモリ上限:デフォルトで 64 MiB に制限されています。大きなバイナリを扱う場合は
wrangler.toml(Workers)やnetlify.tomlの[[edge_functions]].runtime = "wasi"と併せてWASM_MEMORY_LIMIT環境変数で上げられます。 - ファイルシステム:preview2 では読み取り専用領域が提供され、書き込みは不可能です。永続化が必要な場合は KV(Workers)や Netlify の
edge-configを利用してください。 - 最適化の必須:Edge にデプロイする前に
wasm-opt -Ozでサイズを削減すると、ネットワーク待ち時間が約 30 %短縮される実績があります(Cloudflare 内部ベンチマーク参照)。
記事まとめ
- rustup と wasm32‑unknown‑unknown ターゲット を導入すれば、2026 年版ツールチェーンの土台が完成します。
- wasm-pack + cargo-generate による公式テンプレートは、ベストプラクティスを即座にプロジェクトへ組み込め、CI テストも簡単です。
- wasm-bindgen と web-sys を用いれば、文字列・配列や DOM 操作が安全かつシンプルに実装できます。
- Component Model は WIT 定義と
cargo componentで Rust 側の型安全なコンポーネント化を実現し、他言語との相互運用が容易になります。 - Vite/esbuild とデバッグ設定(source‑map・console_error_panic_hook)により、開発中のトラブルシューティングが大幅に楽になります。
- Edge デプロイ(Cloudflare Workers, Netlify Edge Functions)は WASI‑preview2 のサンドボックス強化と組み合わせて、本番環境で安全かつ高速な配信を可能にします。
以上の手順とベストプラクティスに従うだけで、最新ツールチェーンを活用した Rust + WebAssembly 開発がすぐに始められます。ぜひ本ガイドを実践し、自分のフロントエンドプロジェクトに Rust の安全性と高性能を組み込んでください。