
2026/03/08 18:24
申し訳ありませんが、翻訳すべきテキストが提供されていないようです。お手元の文章を共有いただければ、喜んで日本語に翻訳いたします。
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
概要:
本文書は、Rust と WebAssembly の間で安全にデータを交換しつつ、API を明確かつ堅牢に保つためのベストプラクティスガイドラインをまとめたものです。
- Wasm 境界を越える際には値を参照(
)で渡し、必要がない限りエクスポートされた値を消費しないようにします。薄いエクスポートラッパーに&を派生させることは決して行わず、純粋なデータをCopyで包んだ場合のみ許可します。IntoWasmAbi に該当しない型については、ハンドルを安全に保ち、排他性が保証できないときに変更可能にするためにIntoWasmAbiやRc<RefCell<T>>といった内部可変性ラッパーを使用します。Arc<Mutex<T>>- Rust がエクスポートする型には
(例:Wasm*)というプレフィックスを付け、WasmFooをプレフィックスなしの名前に設定します。JavaScript からインポートされるインターフェースはjs_name/js_class(例:Js*)とし、ダックタイピングを許可して Rust 型がそれらのインターフェースを満たせるようにします。JsCharacter- コレクション内で境界を越える必要がある型には、
マクロ(例:#[wasm_refgen])を使用します。#[wasm_refgen(js_ref = JsFoo)]- Rust のエラーを JavaScript に渡すために
を実装し、カスタム名(例:"RwError")のimpl From<YourError> for JsValueを生成します。同様にjs_sys::ErrorもFrom<WasmError>用に実装します。JsValue- Wasm バンドルにビルド情報(Git ハッシュ、ディーティー状態)を含めるには、
内で環境変数を生成し、起動時に出力します(例:build.rs)。#[wasm_bindgen(start)]- 境界を越えて可変参照(
)を渡すことは避け、代わりに内部可変性プリミティブを使用します。&mut を使った手動バインディングは脆弱です;コンパイル時の安全性を確保するためには bindgen が生成したグルーコードに頼ります。js_sys::dyn_into- コレクションで使用されるエクスポート型がコストの低い複製(例:
)を実装し、JS ハンドルから安全に変換できるよう名前空間付きのRcメソッドを公開していることを確認します。clone
この改訂版サマリーはすべての重要ポイントを網羅し、不必要な推測を避けつつ、読者へ主要メッセージを明確に伝えます。
本文
はじめに
近年、Rust で書く Wasm の量が増えてきました。
インターネット上には Wasm に関するさまざまな意見がありますし、wasm‑bindgen も「好き嫌い分かれる」ツールです。経験を積み、その欠点に対処する方法を学ぶうちに、私の作業効率を劇的に上げるパターンが見えてきました。
本記事では、以下の二点についてはっきりさせておきます。
- wasm‑bindgen のメンテナ方針や実装には深く感謝しています。
- ここで紹介する手法よりも優れた方法が存在する可能性があります。これはあくまで私自身の経験に基づくパターンです。
TL;DR(要約)
以下の点を守るだけで、Rust+Wasm の開発はずっと楽になります。
| # | 推奨パターン |
|---|---|
| 1 | Wasm バウンダリを越える際はすべて 参照で渡す。 |
| 2 | よりも / を優先する。 |
| 3 | エクスポート対象の型には を派生させない。 |
| 4 | コレクションで渡す必要がある型は wasm_refgen を使う。 |
| 5 | Rust 側でエクスポートする全ての型に プレフィックスを付け、 はプレフィックス無し名に設定する。 |
| 6 | JS からインポートした型は をプレフィックスに使う。 |
| 7 | Rust 側でエクスポートしたエラー型は を実装し、 へ変換して にする。 |
wasm‑bindgen の仕組みを簡単に
wasm‑bindgen は Rust の構造体・メソッド・関数を JS/TS 側から呼び出せるように、ラッパーコード(glue code)を生成します。
IntoWasmAbi を実装した型は直接 JS へ渡せますが、それ以外の型は Wasm サイドで管理され、JS 側では { __wbg_ptr: 12345 } のようなハンドルとして扱われます。
この仕組みを使う上で重要なのは、JavaScript と Rust が異なるメモリモデル(GC vs. 所有権)を持つことです。
bindgen はそのギャップを埋めるために多くのチェックと変換ロジックを提供しますが、一部は過剰であったり不十分である場合があります。
手動バインディングを書かなければいい?
「手動か自動か」は好みの問題です。
js_sys を使って手作業で型変換する方法もありますが、変更に弱く、コンパイル時のフィードバックが得られません。bindgen の生成コードを利用し、上記のパターンに沿うだけでほとんどの場合は十分です。
名前付けの重要性
名前は「最も難しい二つの問題」の一つ(他方は off‑by‑one エラー)として語られます。
bindgen では、型が JS 側に渡る際の表現を明確にするため、次のような命名規則を採用します。
IntoWasmAbi
型
IntoWasmAbiu32, String, Vec<u8> のように直接 ABI で渡せるプリミティブ型は特別扱いしません。これらは JS と Rust の間で自動的に変換されます。
Rust 側のエクスポート構造体 → Wasm*
Wasm*Rust の列挙や構造体を JS に公開する際、
Wasm* というプレフィックスを付けてラップします。JS 側ではプレフィックスを除去して一意に扱います。
#[derive(Debug, Clone)] pub enum StreetLight { Red, Yellow, Green } #[wasm_bindgen(js_name = StreetLight)] pub struct WasmStreetLight(StreetLight);
JS からインポートしたインターフェース → Js*
Js*extern "C" で宣言された JS の型は、Rust 側ではダックタイプ(任意の実装)として扱います。名前衝突を防ぐためにメソッド名には必ず
js_ プレフィックスを付けます。
#[wasm_bindgen] extern "C" { #[wasm_bindgen(js_name = logCurrentTime)] pub fn js_log_current_time(timestamp: u32); #[wasm_bindgen(js_name = Hero)] type JsCharacter; #[wasm_bindgen(method, getter, js_name = hp)] pub fn js_hp(this: &JsCharacter) -> u32; }
Copy
を派生させない
CopyCopy は値を暗黙に複製してしまうため、ハンドル(リソースへのポインタ)を持つ型がコピーされると「空の」ハンドルになり、後で使用するとランタイムエラーになります。したがって、export 用ラッパーは
Copy を派生させないようにしましょう。
ハンドル破損を防ぐ
wasm‑bindgen は実行時にハンドルが壊れることを検知できません。典型的な失敗例は、所有権を Rust 側で消費してしまうケースです。
#[wasm_bindgen(js_class = Foo)] impl WasmFoo { #[wasm_bindgen] pub fn do_something(&self, bar: Bar) -> Result<(), Error> { ... } }
Bar を所有していると、Rust 側で解放されてしまい、JS からは無効なハンドルが渡ってきます。対策:常に参照渡し(
&)を行い、必要なら内部可変性(Rc<RefCell<_>> 等)を利用する。
&mut
は避ける
&mutJS の非同期実行モデルは Rust のコンパイル時排他チェックを尊重しません。そのため
&mut self を渡すと、再入可能性(re‑entrancy)の問題でランタイムエラーが発生します。代わりに、適切な内部可変性プリミティブ (
RefCell, Mutex) を使用しましょう。
コレクションの扱い
bindgen は
&[T] が IntoWasmAbi の型(JS 管理型)である場合のみ許容します。それ以外の場合は、
Vec<T> という所有型を返す必要がありますが、これは JS 側が配列を自由に変更できるためです。
ワークアラウンド
- エクスポートする型を
/Rc
でクローン可能にする。Arc - 名前空間付きの
メソッドを公開し、JS インターフェースから呼び出せるようにする。clone
を実装して、From<JsType>
の形で簡潔に変換できるようにする。.into()
#[wasm_bindgen(js_name = Character)] pub struct WasmCharacter(Rc<RefCell<Character>>); #[wasm_bindgen(js_class = Character)] impl WasmCharacter { #[doc(hidden)] pub fn __myapp_character_clone(&self) -> Self { self.clone() } } #[wasm_bindgen] extern "C" { type JsCharacter; pub fn __myapp_character_clone(this: &JsCharacter) -> WasmCharacter; }
wasm_refgen を使う
上記の手動作業を自動化するマクロが
wasm_refgen です。#[wasm_refgen(js_ref = JsFoo)] を付与すると、名前空間付き clone が生成され、JS インターフェースとしてインポートされ、From<JsFoo> が自動実装されます。
use std::{rc::Rc, cell::RefCell}; use wasm_bindgen::prelude::*; use wasm_refgen::wasm_refgen; #[derive(Clone)] pub struct WasmFoo { map: Rc<RefCell<HashMap<String, u8>>>, id: u32, } #[wasm_refgen(js_ref = JsFoo)] // ← 自動生成 #[wasm_bindgen(js_class = "Foo")] impl WasmFoo { /* 通常のメソッド */ }
JS エラーへの自動変換
Wasm 側で発生したエラーを
js_sys::Error に変換して JsValue として返すと、JS からは TypeError 等として扱われます。以下のように
From<YourError> を実装すると便利です。
#[derive(Debug, Clone, thiserror::Error)] pub enum RwError { #[error("cannot read {0}")] CannotRead(String), #[error("cannot write")] CannotWrite, } #[derive(Debug, Clone, thiserror::Error)] #[error(transparent)] pub struct WasmRwError(#[from] RwError); impl From<WasmRwError> for JsValue { fn from(err: WasmRwError) -> Self { let js_err = js_sys::Error::new(&err.to_string()); js_err.set_name("RwError"); js_err.into() } }
これで
Result<T, WasmRwError> を返すだけで、JS 側にエラーが投げられます。
ビルド情報の出力
ビルド時に Git のハッシュや dirty ステータスを環境変数に設定し、Wasm 起動時にコンソールへ表示すると、デバッグが格段に楽になります。
Cargo ワークスペースで
build.rs を書き、GIT_HASH 環境変数を設定するサンプルは上記記事のコードをご参照ください。
#[wasm_bindgen(start)] pub fn start() { web_sys::console.info1(format!( "your_package_wasm v{} ({})", env!("CARGO_PKG_VERSION"), build_info::GIT_HASH )); }
まとめ
- 明示的に扱う:ハンドルを渡す際は常に参照で、所有権を消費しない。
- 名前付けを徹底する:
,Wasm*
プレフィックスで型の役割を一目で分かるように。Js* - 内部可変性を活用:
/Rc<RefCell<_>>
でArc<Mutex<_>>
のリスクを回避。&mut - エラーは JS に変換:
を実装し、JS 側に情報が残るように。From<YourError>
これらのパターンを採用すれば、Rust+Wasm 開発は「境界がない」感覚で楽になります。
ぜひ試してみてください。