初級 getting-started

wasm-bindgenの仕組み

wasm-bindgenとは?

wasm-bindgen はRustとJavaScriptの橋渡しです。WebAssemblyはネイティブで4つの数値型(i32i64f32f64)のみをサポートします。それ以外 — 文字列、構造体、配列、オブジェクト — は変換が必要です。wasm-bindgen がこの変換を自動的に行うグルーコードを生成します。

#[wasm_bindgen] アトリビュート

この1つのアトリビュートが、エクスポートやインポートの対象を wasm-bindgen に伝えます:

#[wasm_bindgen]          // 関数: JSにエクスポート
pub fn my_func() {}

#[wasm_bindgen]          // 構造体: JSクラスを作成
pub struct MyStruct {}

#[wasm_bindgen]          // implブロック: メソッドをエクスポート
impl MyStruct {}

#[wasm_bindgen]          // extern "C": JSからインポート
extern "C" {
    fn alert(s: &str);
}

型が境界を越える仕組み

Rust JavaScript コスト 仕組み
i32, f64 number 無料 ネイティブWasm数値型
i64, u64 BigInt 無料 Wasm i64がBigIntに対応
bool boolean 無料 Wasmではi32(0/1)として表現
&str string コピー JSが文字列をUTF-8にエンコードしてWasmリニアメモリに格納
String string コピー RustがUTF-8バイトを書き込み、JSがデコード
&[u8] Uint8Array コピー JSとWasmメモリ間でバイトをコピー
Vec<f64> Float64Array コピー 大量の数値データ転送
Option<T> T | undefined 可変 None はJSで undefined になる
Result<T, E> T またはスロー 可変 Err はJS例外になる
JsValue any 参照 任意のJS値への不透明なハンドル
#[wasm_bindgen] struct class ポインタ JSがWasmメモリ上のポインタを保持
Closure<dyn FnMut()> Function メモリ確保 RustクロージャをJSコールバックとしてラップ

重要なポイント: プリミティブは無料。文字列はコピー。構造体はポインタ。クロージャはメモリ確保が必要。

wasm-packが生成するもの

wasm-pack build を実行すると以下が生成されます:

pkg/
├── my_wasm_bg.wasm       # コンパイル済みWasmバイナリ
├── my_wasm_bg.wasm.d.ts  # Wasmバイナリ用TypeScript型定義
├── my_wasm.js            # JavaScriptグルーコード(型変換を処理)
├── my_wasm.d.ts          # 公開API用TypeScript型定義
└── package.json          # npmに公開可能なパッケージ

.js グルーファイルが処理するもの:

  • .wasm バイナリの読み込みと初期化
  • JS(UTF-16)とWasm(UTF-8)間の文字列エンコード/デコード
  • 文字列と配列のWasmメモリ割り当て管理
  • エクスポートされた構造体の解放後使用の防止
  • 完全な型安全性を備えたTypeScriptフレンドリーなAPIの提供

JavaScript関数のインポート

extern "C" ブロックを使って、RustからあらゆるJavaScript APIを呼び出せます:

#[wasm_bindgen]
extern "C" {
    // window.alert()
    fn alert(s: &str);

    // console.log() — JS名前空間を指定
    #[wasm_bindgen(js_namespace = console)]
    fn log(s: &str);

    // リネーム:Rust名とJS名を異なるものに
    #[wasm_bindgen(js_name = "setTimeout")]
    fn set_timeout(closure: &Closure<dyn FnMut()>, ms: u32);

    // グローバル変数のインポート
    #[wasm_bindgen(js_name = "document")]
    static DOCUMENT: web_sys::Document;
}

よく使うアトリビュートパターン

コンストラクタ — JSで new MyStruct() を使えるようにする

#[wasm_bindgen(constructor)]
pub fn new() -> Self { Self { count: 0 } }

ゲッター / セッター — JSで obj.name を使えるようにする

#[wasm_bindgen(getter)]
pub fn name(&self) -> String { self.name.clone() }

#[wasm_bindgen(setter)]
pub fn set_name(&mut self, name: &str) { self.name = name.to_string(); }

js_name — JS規約に合わせてリネーム

#[wasm_bindgen(js_name = "calculateTotal")]
pub fn calculate_total(&self) -> f64 { /* ... */ }

skip — publicメソッドをJSから隠す

#[wasm_bindgen(skip)]
pub fn internal_method(&self) { /* エクスポートされない */ }

Cargo.tomlの設定

すべてのRust/Wasmプロジェクトに必要な依存関係:

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

[dependencies]
wasm-bindgen = "0.2"
# オプション: ブラウザAPIバインディング
web-sys = { version = "0.3", features = ["Window", "Document"] }
# オプション: JS組み込み型
js-sys = "0.3"
# オプション: async/awaitサポート
wasm-bindgen-futures = "0.4"

試してみよう

スターターコードは3つの主要パターンを示しています:関数のエクスポート、構造体のエクスポート、JS関数のインポート。この3つのパターンで実際のWasm開発の90%をカバーできます。

試してみる

チャプタークイズ

すべての問題に正解してレッスンを完了しましょう