ハクソク

世界を動かす技術を、日本語で。

Rustを用いたWasmの執筆に関するノート

概要

  • RustとWasmの連携におけるwasm-bindgen活用パターンの紹介
  • 参照渡し命名規則など、痛みを減らす実践的アドバイス
  • Copyの禁止や**Rc<RefCell<T>>/Arc<Mutex<T>>**の推奨
  • wasm_refgenネーミング規則の重要性
  • バインディングの設計とメモリ管理の注意点を解説

Rust + Wasm開発を快適にするwasm-bindgenパターン

  • wasm-bindgenはRust構造体・関数をJS/TSから呼び出すためのグルーコード自動生成ツール
  • RustとJavaScript異なるメモリ管理モデル(Rust:所有権/借用、JS:GC/非同期)を同時に扱う必要性
  • wasm-bindgen制約や落とし穴を理解し、実践的なパターンで開発効率向上

主な推奨パターン

  • Wasm境界を超える値は&参照で渡す
  • **&mutではなくRc<RefCell<T>>やArc<Mutex<T>>**を利用
  • Copyトレイトはエクスポート型に付けない
  • コレクションで境界を超える型はwasm_refgenを使う
  • Rustエクスポート型はWasm*プレフィックス、js_name/js_classでJS側名統一
  • JSインポート型はJs*プレフィックス
  • Rustエラー型はFrom<YourError> for JsValueを実装しjs_sys::Errorを活用

命名規則

  • Wasm*型:Rustからエクスポートする構造体・enumのラッパー
    • 例:pub struct WasmStreetLight(StreetLight)
    • Rust側でWasm*を付け、JS側ではそのままの名前で扱う
  • Js*型:JSからRustへインポートする型に付与
    • 例:type JsCharacter;
    • メソッド名もjs_*で接頭辞を付けることで命名衝突防止

IntoWasmAbi型

  • プリミティブ型(u32, String, Vec<u8>等)はIntoWasmAbiを実装し、特別な処理不要

Copy禁止の理由

  • Copyを付与すると、リソースハンドルの重複コピーが発生し、ヌルポインタやメモリ破壊の原因
  • 純粋なデータ型のみCopy許可、ハンドル型は必ずCloneのみ

ハンドル破壊防止

  • Rust側で所有権を消費(move)してしまうと、JS側ハンドルが不正参照に
  • 常に&参照で渡し、内部可変性(Rc<RefCell<T>>やArc<Mutex<T>>)で管理
  • Wasm境界を超えた後も、JS/Rust双方で生存期間を意識

&mutの回避

  • JSのasync/再入性により、Rustの&mutセーフティが保証されない
  • 排他性が証明できない場合は、&mutではなく内部可変性プリミティブを利用

コレクション型の制約と回避策

  • wasm-bindgenは&[T]やVec<&T>等、IntoWasmAbi未実装型のコレクション受け渡しを禁止
  • Rc/ArcでCloneを安価にし、クローン用メソッドをエクスポートし、JS側で.into()変換
  • Duck TypingでJSインターフェースをRustにインポートし、柔軟な型受け渡し

マニュアルバインディングの是非

  • js_sys等を使った手動変換は柔軟だが、型変更時にコンパイラの恩恵を受けにくく非推奨
  • wasm-bindgenの自動生成グルーを活用することで、型安全性・保守性向上

エラー型の扱い

  • Rustエラー型はFrom<YourError> for JsValueを実装し、js_sys::ErrorとしてJS側へ返却
  • エラー伝播の一貫性とJS側での扱いやすさを確保

まとめ:Rust+Wasm開発の心得

  • wasm-bindgenの制約とメモリ管理モデルの違いを理解
  • 参照渡し・内部可変性・命名規則・エラー伝播のパターンを徹底
  • JS/Rust間の型・リソース管理の明示的設計が重要
  • これらのパターンを守ることで、Rust+Wasm開発の痛みを大幅に軽減可能

Hackerたちの意見

これって、Emscriptenのembindを使ってC++のJSバインディングを自動生成する時の問題と同じように感じるな。俺のアドバイスは「やらない方がいい」ってこと。手書きのJS関数で非自明な作業を行う適切なハイブリッドC++/JSアプリを書くのに比べて、すごく複雑さと無駄が増える。境界のどちら側にどのコードを置くかのバランスを見つけるには経験が必要だけどね。C++やRustの型を直接JSにマッピングしようとするんじゃなくて、きちんと設計されたC APIを通す方がいいよ(例えば、複雑なC++/Rustオブジェクトを境界を越えて渡そうとしない方がいい。C++/RustとJSの型システムには重なりが少なすぎるから)。自動バインディングのアプローチは、C APIにはもっと意味があるけど、C++やRustみたいに「セマンティックサーフェス」が広いネイティブAPIには向かないね。
WASMって、Javaのバイトコードみたいなもんだよね?ランタイムが必要なコンパイルターゲット?でも、もっとサンドボックス化されてる?
一般的に言って、ちょっと手を振りながら詳細を省略すると、そうだね :)
低レベルで、GCはオプション、スタックに割り当てられたユーザー定義型をサポートしてる。最初はCとC++向けにLLVMバックエンドとして設計された。
WASPはほぼJava Appletsと同じだけど、ブラウザに組み込まれていてプラグインなしで動く。Java Appletsはすごい技術だったと思う。WindowsのJavaランチャーが台無しにしたけど(それが主なセキュリティ問題だったと理解してる)。ちなみに、Java 8はまだ動くよ :P
Wasm 1.0と2.0だけを考えると、「ランタイム」の量は比較的少ないよ。wasm2c(または俺のwasm2go)みたいなツールがそれを示してる。大きなランタイムを持ち運ぶ必要はなくて、WasmのバイトコードをC(またはGo)に直接変換するだけなんだ。wasm2c: https://github.com/WebAssembly/wabt/blob/main/wasm2c/README.... wasm2go: https://github.com/ncruces/wasm2go
Rust/Wasmのツールベルトにあると便利なのがtsify[0]で、普通のデータの型安全を確保するのに役立ってる。serdeデータ構造を更新して、再コンパイル後のTypeScriptエラーをすぐに確認できるのがすごく便利。 [0]: https://github.com/madonoharu/tsify
WASMで直接コードを書くことについての記事を期待してたんだけど。タイトルがちょっと誤解を招くね。
面白いね…もしRust/TSバインディングでのMCP作業を見たいなら、ここに俺が昔書いた実装があるよ https://github.com/modelcontextprotocol/modelcontextprotocol... https://github.com/modelcontextprotocol/rust-sdk/pull/183
ちょっと脱線するけど、Wasmパーサー(正確にはWasmバイナリフォーマットのデコーダー)をゼロから作ることにしたんだ。WasmとRustを学ぶための手段としてね。こういうのを書くのは初めてだったけど、仕様書はすごく明確で読みやすかったよ。面白いことに、テスト用のトイパーサーが、約4ヶ月前にリリースされた仕様のバージョン3で実際の回帰を引き起こしたのには驚いた。[0] https://github.com/agis/wadec [1] https://github.com/WebAssembly/spec/issues/2066
誰かWASMをGC言語で使ってる人いる?最近のWASMでそのサポートが追加されたと思うけど、それで使える言語の景色は変わったのかな?
いくつかは存在するよ: https://spritely.institute/hoot/
現在の選択肢に全然満足してないから、新しいWasm GC言語を作ってるんだ。3ヶ月経ったけど、今のところはまあまあ良い感じ。WASMがGCの部分を全部管理してくれるけど、その代わりにref、struct、arrayの型を全部使わないといけない。vtableを作るのは簡単だけど、インターフェース用のファットポインタにはオブジェクトが必要だから、自分でポインタのレイアウトを作ることはできない。WebのコンテキストではGCは素晴らしいはずで、ブラウザのGCがDOM、JS、WASMを跨いで動作できるから、JS経由で取得したノードへの参照を持っておけるし、ノードをDOMから削除して参照を捨てれば、JSと同じように収集される。大きな欠点は、Wasmの境界を越えるデータ転送がGCでより多くのコピーを必要とすること。バイト配列(array i8)みたいなものは外部からは完全に不透明だから、データを読むために個別のバイトアクセス関数を提供する必要がある。WASI側では、線形メモリにしか落とせないから、最初からスペースを確保して、GCのstructやarrayにコピーしないといけない。文字列はエンコーディングの問題もあって二重に厄介だ。GCはまだマルチスレッドをサポートしていないし。これらの問題は、https://github.com/WebAssembly/design/issues/1569 やWASIがGC型に落とすことで修正される予定だけど、stringrefは素晴らしかったのに、今は死んでるみたい。だから、これらの問題が解決されることに期待してるけど、GCは今のところちょっと二級市民みたいな感じだね。
いや、ブラウザでGCが欲しいなら、すでにJSやTS(JSリンターとして)を使ってるから。ブラウザの外では、JVMやCLRランタイムのGCはWASMのGCよりずっと進んでるよ。
.NETのBlazorクライアントサイドフレームワークはC#のWASMだね。
WASMにコンパイルされるほとんどのGC言語は、WASMの組み込みGCサポートを使ってないと思うよ。ただGCをWASMにコンパイルしてるだけだね。GoやJava、C#みたいな言語はWASMのサポートがあるよ。
wasmの世界は少しずつ良くなってきてるよ。wasmのCスタイルのインターフェースは、高レベルのインターフェースを設計するにはかなり制限があるから、wasm-bindgenが必要なんだ。幸いなことに、Firefoxがwasmコンポーネントモデル提案に基づいて、すべてのWeb APIを直接wasmに公開するための初期提案を進めてる。詳しくは見てみてね。https://hacks.mozilla.org/2026/02/making-webassembly-a-first...
正直なところ、文字列のマーシャリングのパフォーマンス改善が示された以外、WASMコンポーネントモデルをブラウザに統合することが良いことだとは思えない。特定のニッチなユースケースのために多くの複雑さがあるから(文字列変換のオーバーヘッドは減るけど、境界を越えて大量の文字列を渡すのは、DOM APIの1:1マッピングをする時だけだし)。WebGPUやWebGLみたいなWeb APIが同じようなパフォーマンス改善を得られるとは思えないし、WASMからWebGPUにアクセスするためのもっと重要なパフォーマンス問題がWASMコンポーネントモデルでどう解決されるのかも不明だよ(例えば、WebGPUはWGPUBufferの内容を別のJS ArrayBufferオブジェクトにマッピングするけど、WASMから直接アクセスするにはデータをWASMヒープに出し入れしないといけない)。
> 参考までに、私はstdの`futures::lock::Mutex`か、no_stdの下での`async_lock::Mutex`が好きだな。Rustの非同期ミューテックスには多くの落とし穴があって、最近はそれをコードの匂いだと考えるようになったよ。例えば、Oxideプロジェクトが遭遇した問題を見てみて。[1] 私の経験では、非同期でミューテックスを待つことが意味を持つケースは比較的少なくて、イールドポイントでミューテックスを保持する意味があるケースはほとんどないから、多くの人が反対のアドバイスにもかかわらず非同期ミューテックスに頼る理由がわかる。[2] それらは構造化された並行性とは基本的に互換性がないけど、Rustの非同期は借用チェッカーと上手くやるために構造化されることを本当に望んでいる。`shadow-rs` [3] は、後で投稿で言及されるビルド情報収集のためのプリビルドの方法として言及する価値があるよ。[1]: https://rfd.shared.oxide.computer/rfd/0609 [2]: https://docs.rs/tokio/latest/tokio/sync/struct.Mutex.html#wh... [3]: https://docs.rs/shadow-rs/latest/shadow_rs/
私はtokioを完全に避けるようにしてる。embassyを使った組み込みのユースケースは理解できるけど、コア数以上のスレッドを使う必要があったことはないんだ。そういうユースケースがあるのは認めるけど、実際には出会ったことがないな。私は通常、I/Oよりも計算に時間を使うけど、しっかりしたライブラリが非非同期のブランチを捨てちゃったから、思ったよりも頻繁に使わざるを得ないんだ。ちょっと愚痴っぽくなっちゃったけど、そんなに気にするならブランチをフォークすればいいんだよね。でも、文句を言う方が楽だし。
記事の著者だよ!実は、あの記事を書いた後に君の意見に賛成するようになったんだ。一般的にミューテックスはあまり好きじゃないし、Haskellの頃のTVarsみたいなものが恋しいよ。関わってないし、実際には使ってないけど、もっと探求してほしいデッドロックフリープロジェクトを紹介しておくね:https://crates.io/crates/happylock
RcやArcに関するアドバイスは、Gtk-rsの時と同じ感じだね。clone!()が出てくるまでは。これが私を遠ざけた理由の一つで、エルゴノミクスはまだRustの2026年ロードマップの一部に過ぎないよ。