二十年以上にわたり、JavaScript はウェブの押しも押されもせぬ言語でした。しかしブラウザの実行環境は、もはや単一言語の場ではありません。WebAssembly は静かに、あらゆる現代的ブラウザにおける二つ目のランタイムとなり、JavaScript だけでは効率よく届けられない能力を可能にしています。画像エディタ、動画コーデック、データベースエンジン、3D レンダラ、言語ランタイム——それらはいまやブラウザのタブの中で動いています。ネイティブに近い速度で走る低水準のバイナリ形式へとコンパイルされて。
WebAssembly とは何か、なぜ重要か
WebAssembly は、スタックベースの仮想機械で実行される低水準のバイナリ命令形式です。プログラミング言語にとって可搬なコンパイル先として設計され、クライアントとサーバー双方のアプリケーションをウェブへ届けられるようにしました。四つのブラウザベンダー——Google、Mozilla、Apple、Microsoft——が設計に協働し、2019 年に W3C の標準となりました。主要なブラウザはすべて 2017 年から対応しています。
とはいえ WebAssembly は JavaScript の置き換えではなく、補完です。JavaScript は引き続き DOM 操作、イベント処理、UI ロジックの言語であり続けます。WebAssembly は、JavaScript が元来想定していなかった計算負荷の高い仕事を担います。二つのランタイムは同じタブに共存し、モジュールは JavaScript の関数を取り込み、JavaScript は明確に定義された API を通じて Wasm のエクスポートを呼び出します。
WebAssembly は JavaScript を置き換えようとはしていません。JavaScript が効率よく解けない問題を解こうとしているのです。開発者が同じアプリケーションの中で仕事ごとに適した道具を選べるようになったぶん、ウェブプラットフォームは強くなりました。
基礎:バイナリ形式と線形メモリ
WebAssembly のモジュールは、解析の速さと転送量の小ささを狙って設計されたバイナリ形式(.wasm ファイル)を使います。この形式は簡潔で、典型的なモジュールは同等の JavaScript を最小化したものよりもかなり小さくなります。型、関数、インポート、エクスポート、メモリ宣言、命令を密な表現に符号化し、ブラウザは一度の線形な走査で検証とコンパイルを済ませられます。
どの WebAssembly モジュールも、概念上のスタックマシン上で動きます。命令は評価スタックに値を積み、演算のために取り出します。扱える値の型は i32、i64、f32、f64——32 ビットと 64 ビットの整数および浮動小数点数だけです。Wasm の水準には文字列もオブジェクトも配列も、その他の高水準な型もありません。複雑なデータ構造はすべて、線形メモリ上の生のバイト列として表さなければなりません。
線形メモリは WebAssembly でもっとも重要な概念です。各モジュールは、連続していて拡張可能なメモリ領域を一つ以上宣言できます。このメモリは平坦なバイト配列で、モジュールはロードとストアの命令で読み書きします。ホスト(JavaScript ランタイム、あるいは Wasm ランタイム)が初期サイズと最大サイズを管理し、1 ページ 65,536 バイトの追加を要求して拡張できます。ホスト側からは ArrayBuffer として見えるので、JavaScript はモジュールが使うのとまったく同じメモリを読み書きできます。
この共有メモリのモデルこそが WebAssembly を効率的にしています。ホストとモジュールは直列化も複製もせずにデータをやり取りします——同じバッファに書くだけです。Rust の関数が JavaScript に文字列を返すとき、関数はそのバイト列を線形メモリに書き込み、ポインタ(整数のオフセット)と長さを返します。JavaScript はそのバイト列を ArrayBuffer から直接読みます。JSON の解析も、メッセージの受け渡しも、関数呼び出し以上の負担もありません。
モジュールの構造と検証
WebAssembly のモジュールはいくつかのセクションで構成されます。型セクションは関数の署名を定め、関数セクションは関数を宣言し、コードセクションが実際のバイトコードを持ち、メモリセクションが線形メモリを定義し、エクスポートセクションが関数とメモリをホストに公開します。モジュールが実行される前に、ブラウザは構造を検証し、型安全性を強制します。検証は、すべての命令のオペランドが期待される型に合っていること、すべての関数呼び出しが有効な署名を参照していること、すべてのメモリアクセスが宣言された範囲に収まっていることを保証します。これは数ミリ秒で終わり、不正なモジュールがランタイムを悪用できないことを担保します。
セキュリティモデルは明示的で、範囲が絞られています。WebAssembly のモジュールは、ホストがインポート関数を通じて明示的に能力を与えないかぎり、DOM にも触れられず、ネットワーク要求も、ファイルの読み取りも、システムとのやり取りもできません。モジュールはサンドボックスの中で動き、自らの線形メモリとインポート関数表の外にはいっさい手が届きません。そのため、信頼できない出所のモジュールでも安全に実行できます——ブラウザでもサーバーでも決定的に重要な性質です。
Wasm へのコンパイル:Rust、C、Go、Zig の比較
WebAssembly へのコンパイル先としての質は、言語によってかなり違います。ある言語は無駄のない効率的なモジュールをほとんど負担なく生みます。別の言語は、バイナリに数メガバイトを上乗せするランタイムを必要とします。どの言語を選ぶかは、性能上の要件、チームの習熟、そして必要な統合の複雑さによります。
Rust:Wasm の基準点
WebAssembly の対応は、どの言語と比べても Rust がもっとも優れています。wasm-pack のツールチェーンが、コンパイル、最適化、JavaScript バインディングの生成を滑らかに引き受けます。Rust が小さなバイナリを生むのは、ガベージコレクタも重いランタイムも持たず、所有権のモデルが線形メモリの管理に自然に写るからです。いくつか関数をエクスポートする典型的な Rust のモジュールは、LTO と最適化のあと 10 KB から 50 KB に収まり、手書きの Wasm と張り合えます。
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
fn alert(s: &str);
}
#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u32 {
match n {
0 => 0,
1 => 1,
_ => fibonacci(n - 1) + fibonacci(n - 2),
}
}
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
format!("Hello, {}! Wasm says hello.", name)
}wasm_bindgen クレートは、線形メモリをめぐる面倒な段取りを自動で引き受ける JavaScript の橋渡しコードを生成します。JavaScript から fibonacci を呼ぶ体験は、ほかの JavaScript 関数を呼ぶのと変わりません。裏では wasm_bindgen が文字列の引数をポインタと長さの組に変換し、Wasm の関数を呼び、結果を JavaScript の文字列へ戻しています。この抽象化の層はごくわずかな負担しか加えず、Rust と Wasm の組み合わせを日常的に使えるものにしています。
Emscripten 経由の C と C++
Emscripten は Wasm 向けの最初のコンパイル用ツールチェーンです。LLVM をバックエンドに C と C++ のコードを Wasm へコンパイルし、既存のネイティブアプリケーションをわずかな変更でウェブへ移植できる、広範な POSIX 互換層を備えています。仮想ファイルシステム、OpenGL から WebGL への変換、pthread のエミュレーション、そしてモジュールのライフサイクルを扱う JavaScript ランタイムが含まれます。
公式 Wasm バックエンドを備えた Go
Go は 1.11 で WebAssembly 対応を加え、リリースごとに改善を続けています。Go のプログラムを Wasm へコンパイルするのは簡単で、GOOS=js GOARCH=wasm を設定して go build を走らせるだけです。ただし Rust と比べると、Go の対応には大きな制約があります。Go はランタイム(goroutine のスケジューラ、ガベージコレクタ、defer スタック)をすべてのバイナリに含めるため、最小限のモジュールでも 2 MB 前後から始まります。サイズがさほど問題にならないサーバーサイドなら許容できますが、ダウンロードと解析の時間が決定的なブラウザ用途では重すぎます。
Zig:新たな挑戦者
Zig は、WebAssembly を第一級で支える現代的な C の代替を名乗っています。Zig のコンパイラは Emscripten のような包み紙なしに、直接 Wasm を出力できます。サイズと性能で Rust と競える水準のモジュールを生み、comptime の機能によって、実行時の負担を丸ごと取り除けるメタプログラミングも可能です。Zig は隠れたランタイムを抱えません——明示的にリンクしないかぎり、ガベージコレクタもアロケータも起動コードもありません。
- Rust はもっとも小さな Wasm バイナリ(典型的なモジュールで 10〜50 KB)を生み、wasm-pack と wasm-bindgen という最良のツール環境を備えています。
- Emscripten を通じた C と C++ は、既存のネイティブコードを Wasm へ移す最良の選択肢です。互換性は最も高い一方、バイナリは大きくなります。
- Go は容易に Wasm へコンパイルできますが、ランタイムを同梱するため大きなバイナリ(2 MB 超)になり、サーバーサイド向きです。
- Zig は実行時の負担のない引き締まったモジュールを生み、最小構成や組み込み用途に向いています。
ブラウザで Wasm を使う
ブラウザで WebAssembly のモジュールを読み込んで実行する手順は、現代的なブラウザすべてで共通する標準 API に従います。JavaScript の API は WebAssembly 名前空間に compile、instantiate、instantiateStreaming を備えます。ストリーミング版が望ましいのは、バイト列をダウンロードしながらコンパイルを始め、ネットワーク入出力とコンパイルを重ねられるからです。
async function loadWasm(url: string) {
const response = await fetch(url);
const results = await WebAssembly.instantiateStreaming(response);
return results.instance.exports;
}
async function main() {
const wasm = await loadWasm("/fibonacci.wasm");
console.log(wasm.fibonacci(40));
}
main().catch(console.error);instantiateStreaming は、instance プロパティ(コンパイル済みのモジュールインスタンス)と、モジュールがエクスポートとして宣言した関数やメモリをすべて収めた exports オブジェクトを持つオブジェクトを返します。第二引数として imports オブジェクトを渡すこともでき、モジュールがコンパイル時に期待していた JavaScript の関数や値へのアクセスを与えられます。
本番では通常、ビルド時にモジュールを事前コンパイルするか、バンドラのプラグインを使います。Webpack 5 は Wasm のインポートをそのまま扱え、Vite には実験的な対応があります。これらのバンドラは取得、インスタンス化、ライフサイクルの管理を自動で行い、Wasm のモジュールを JavaScript のモジュールのように読み込めるようにします。wasm-pack を使うときの書き方は import init from './pkg/fibonacci.js' で、橋渡しコードをすべて引き受ける Promise ベースの初期化関数が得られます。
ブラウザで気をつけたいのが最初のコンパイルです。Wasm のモジュールは初回読み込み時にバイナリ形式からネイティブコードへコンパイルされます。小さなモジュールなら数ミリ秒です。10 MB を超えるような大きなモジュールでは数百ミリ秒かかることがあり、メインスレッドを止めてしまいます。解決策は Web Worker の中で WebAssembly.compileStreaming を使うことです。メインスレッドの外でコンパイルし、メインスレッドが即座にインスタンス化できるコンパイル済みモジュールを返します。大きなモジュールを読み込む本番アプリケーションには欠かせない作法です。
WASI とサーバーサイドの Wasm
WebAssembly はもともとブラウザ専用として設計されましたが、そのセキュリティモデルと性能特性はサーバー用途にも同じくらい魅力的です。難しいのは、ブラウザが DOM、fetch、WebSocket といった固有のホスト API を提供するのに対し、サーバー環境にはそれがないことです。WebAssembly System Interface は、ブラウザの外で Wasm モジュールが使える POSIX 風のシステムコールを標準として定めることで、これを解きます。
WASI はファイル入出力、ネットワーク、時刻の取得、乱数生成、環境変数、コマンドライン引数の扱いに抽象を与えます。WASI 対応でコンパイルされたモジュールは、ファイルを読み、ネットワークソケットを開き、WASI 準拠のどのランタイムでも通用する標準化された窓口を通じて OS とやり取りできます。これが Wasm をサーバーアプリケーションとして動かす土台です。
WASI の標準は複数のスナップショットを経て進んでいます。WASI preview 1 は、今日ほとんどのツールチェーンが支える安定した基準線です。WASI preview 2 はコンポーネントモデルを意識した設計と、ケイパビリティに基づくセキュリティを導入し、各モジュールが必要とするシステム資源を明示的に宣言します。preview 2 が大きな前進であるのは、細かい粒度の権限モデルを可能にするからです。特定のファイルを読むだけのモジュールは、システム上のほかのファイルにいっさい触れられません。
WASI は WebAssembly を、ブラウザの技術から普遍的なサンドボックス付きランタイムへと変えます。ブラウザのタブで動くのと同じモジュールが、サーバーサイドの Wasm ランタイムでも、サーバーレス関数でも、エッジのノードでも——変更なしに動きます。その「どこでも」が JavaScript の届かない環境まで含むとき、「一度書けばどこでも動く」は新しい意味を持ちます。
Wasm のランタイムと配備
ブラウザの外で WebAssembly を動かすには、サーバーサイドの Wasm ランタイムが要ります。成熟した実装がいくつかあり、設計思想も用途も異なります。重要なのは Wasmtime、Wasmer、そして組み込み向けの WAMR(WebAssembly Micro Runtime)の三つです。
Wasmtime
Wasmtime は、WASI 標準を推進するのと同じ Bytecode Alliance が開発する独立したランタイムです。Rust で書かれ、Wasm のために設計されたコード生成器 Cranelift でモジュールをコンパイルし、Rust、C、Python などの API を備えます。サーバーサイドの Wasm でもっとも広く使われるランタイムで、Cloudflare Workers、Fastly Compute@Edge をはじめ、いくつものエッジ計算基盤の背後にあります。
use wasmtime::*;
fn main() -> Result<()> {
let engine = Engine::default();
let module = Module::from_file(&engine, "hello.wasm")?;
let mut store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &module, &[])?;
let hello = instance
.get_typed_func::<(), ()>(&mut store, "hello")?;
hello.call(&mut store, ())?;
Ok(())
}Wasmtime の組み込み API は端正で素直です。Engine(コンパイル環境)を作り、ファイルやバイト列から Module を読み込み、Store(Wasm の線形メモリを保持する実行文脈)を作り、モジュールをインスタンス化してエクスポートされた関数に触れます。メモリ管理、トラップの処理、WASI のシステムコール転送は、ランタイムが自動で引き受けます。
Wasmer
Wasmer も広く使われるランタイムで、複数のコンパイルバックエンド——Cranelift、LLVM、そして最適化を抑えて素早く JIT する Singlepass——に対応します。Rust、C、C++、Python、Go、PHP、Ruby、Java などの SDK を備え、ほぼどんなプログラミング環境からも使えます。さらに、npm に似た Wasm モジュール配布のためのパッケージレジストリ(WAPM)も提供しています。
Wasmer と Wasmtime の要点の違いは、Wasmer が開発体験と組み込みやすさを重んじ、Wasmtime が標準適合とセキュリティを重んじることです。どちらも優れた選択で、エコシステムの広さ(Wasmer)を取るか、厳密な仕様適合と本番での実績(Wasmtime)を取るかで決まります。
- Wasmtime:本番のサーバーサイド Wasm に最適。WASI への厳密な適合、Cranelift の JIT コンパイラ、Cloudflare と Fastly での採用実績。
- Wasmer:Rust 以外のアプリケーションへ Wasm を埋め込むのに最適。LLVM でのコンパイルによりネイティブに近い速度が得られ、WAPM のパッケージ環境を備えます。
- WAMR:IoT や組み込み機器に最適。ごく小さな設置面積、インタプリタと JIT の両モード、iwasm コマンドライン道具付き。
性能の特徴と、Wasm が勝つ場面
WebAssembly が JavaScript に対して持つ性能上の優位は、仕事の性質によります。CPU 律速で数値計算の重い処理では、Wasm はおおむね 1.5 倍から 3 倍速く動きます。画像処理やデータ圧縮のようなメモリ集約的な処理では、Wasm がメモリ配置と確保の仕方を直接制御できるぶん、差はさらに開くことがあります。入出力中心や DOM 中心の仕事では優位はなく、モジュールと JavaScript ホストのあいだを渡る費用のぶん、かえって重くなることもあります。
コンパイルのモデルも要因です。JavaScript は JIT を使い、すぐ実行を始めてから、よく通る経路を時間をかけて最適化します。Wasm は事前、あるいはモジュール読み込み時にコンパイルされるため、はじめから最高性能に達します——暖機の時間がありません。短命な関数や一度きりの実行では、初回呼び出しで JavaScript を遅くする JIT の暖機遅延がなくなります。
Wasm が本当に優れているのは予測可能性です。JavaScript の JIT 最適化は決定的ではありません。同じ関数でも、受け取る型、呼ばれた回数、コンパイラの発見的規則の状態によって最適化されたり、取り消されたりします。Wasm の実行は決定的です。どの命令にも決まった費用があります。ガベージコレクションによる停止も、型の取り違えも、最適化の破棄もありません。だから、理論上の最大スループットより遅延の安定が重い仕事——音声処理、物理シミュレーション、金融計算——には Wasm が正しい選択になります。
- Wasm が勝つ場面:画像と動画の処理、音声の合成と解析、データの圧縮と展開、暗号処理、ゲームエンジン、物理シミュレーション、データベースのクエリ実行。
- JavaScript が勝つ場面:DOM の操作、イベント処理、ネットワーク要求の取りまとめ、フレームワークによる UI 描画、文字列とテキストの処理、小さい、あるいはめったに呼ばれない補助関数。
- 境目は動いています。WASI とコンポーネントモデルによって Wasm がウェブ API に手を伸ばすほど、多くの仕事が Wasm へ移りますが、JavaScript は依然として主要な接点の層であり続けます。
コンポーネントモデルと Wasm の未来
WebAssembly のコンポーネントモデルは、最初の公開以来もっとも大きな進化です。初期の Wasm の根本的な制約——モジュールが孤立した島であり、互いに接続する標準的な手立てがなかったこと——に応えるものです。コンポーネントモデルは、高水準で言語に依らないインターフェースの体系を導入し、Wasm モジュールを積み木のように組み合わせられるようにします。
いまのモデルでは、モジュールがエクスポートする関数は整数と浮動小数点数しか受け取れず、返せません。文字列や配列、複雑なデータ構造を別のモジュールへ渡すには、双方がメモリ配置の約束を共有しなければなりません——線形メモリの領域を分け合い、確保と解放を調整する必要があります。これは壊れやすく、言語ごとに違います。コンポーネントモデルは、文字列、レコード、バリアント、リスト、入れ子のデータ構造をモジュールの境界を越えて扱う、標準化されたインターフェース型の体系でこれを解決します。
インターフェース型は、コンポーネント間を流れるデータを言語に依らない形で記述します。あるコンポーネントが、文字列を受け取って整数フィールドを二つ持つレコードを返す関数をエクスポートすれば、Rust で書かれていようと C や Go で書かれていようと、ほかのどのコンポーネントも相手の内部メモリ配置を何も知らないまま、その関数を呼べます。ランタイムが変換の論理を自動で用意し、呼ぶ側と呼ばれる側の表現のあいだを翻訳します。
実際の事例
WebAssembly は机上の技術ではありません。今日の本番でもっとも要求の厳しいウェブアプリケーションのいくつかを動かしており、道具が成熟し性能上の利点が否定しがたくなるにつれて、採用は加速しています。
Figma:Wasm の先駆者
Figma はもっともよく知られた成功例です。中核の描画エンジンは C++ で書かれ、Emscripten を通じて Wasm へコンパイルされています。Figma は C++ のコードベース全体——グラフィックスライブラリ Skia、テキスト整形の HarfBuzz、自社のレイアウトエンジンを含めて——を一つの Wasm モジュールにまとめています。JavaScript の層は入力イベントと DOM の管理を担い、Wasm が描画、当たり判定、レイアウト計算のすべてを担います。結果として、ネイティブのデスクトップアプリに迫る性能でブラウザのタブの中を動くデザイン道具が生まれました。
SQLite:ブラウザの中のデータベース
SQLite はおそらく世界でもっとも広く配備された Wasm モジュールです。プロジェクトは公式の Wasm ビルドを配布しており、ブラウザの中で完全な SQLite のエンジンが動きます。利用者はサーバー側の部品なしに、クライアントだけで SQLite のデータベースに問い合わせられます。このビルドは Emscripten のツールチェーンで作られ、データベースを IndexedDB に永続化するための仮想ファイルシステム対応を含みます。
その含意は小さくありません。クライアント側での問い合わせ能力が要るアプリケーション——データ分析の道具、レポートの画面、科学計算のノートブック——は、Wasm 版 SQLite を使って複雑な集計、結合、ウィンドウ関数をブラウザの中で直接実行でき、データをサーバーへ送る必要がありません。sql.js や better-sqlite3-wasm といったライブラリが SQLite の Wasm モジュールを端正な JavaScript の API で包み、ほかの npm パッケージと同じ手軽さで使えるようにしています。
Google Earth:3D 描画のための Wasm
ブラウザ版の Google Earth は、3D 地形の処理とデータ展開の経路を Wasm で作り直しました。利用者が地図を動かすあいだ、Wasm モジュールは圧縮された二進データを受け取り、Wasm へコンパイルされた最適化済みの C++ アルゴリズムで展開し、幾何データを描画可能なメッシュに変え、結果を WebGL へ渡します——すべてを一枚のアニメーションフレームの予算の中で、データ複製の負担なしに行います。
- Figma は C++ の描画エンジンに Wasm を使い、ブラウザ上のベクターグラフィックス編集でネイティブ級の性能を実現しています。
- SQLite は公式の Wasm ビルドを配布し、クライアント側で完全なデータベースエンジンを動かして、サーバーなしのオフライン問い合わせを可能にしています。
- Google Earth は、流れてくる地理空間データの実時間展開と幾何処理を毎秒 60 フレームで行うために Wasm に頼っています。
- Adobe はウェブ版 Photoshop で、画像フィルタと色空間変換に Wasm(Emscripten でコンパイルした C++ コード)を使っています。
- Zoom はウェブクライアントで、映像の復号と背景のぼかしに Wasm を使っています。
エッジとサーバーレスにおける Wasm
サーバーレスの基盤がエッジ計算のランタイムとして WebAssembly を採ったのは、コンテナに対するコールドスタートの優位があるからです。Wasm のモジュールはミリ秒ではなくマイクロ秒で起動します。起動すべき OS も、分岐すべきプロセスも、初期化すべきランタイムもないからです。コールドスタートの遅延が利用体験を直接左右する基盤にとって、これは質の違いです。
Cloudflare Workers は Wasm を取り入れた最初の大きな基盤でした。Workers は JavaScript と Wasm モジュールの双方を実行できる V8 の隔離環境で動きます。計算のロジックを Wasm モジュールに置いた Worker は、コールドスタートから 5 ミリ秒未満で要求に応え始められます。Worker の環境は Service Worker 仕様に基づく限られた API——fetch、Cache、KV ストレージ、Durable Objects——を提供し、WASI 対応でコンパイルされた Wasm モジュールはホスト環境のインポート機構を通じてそれらを使えます。
Fastly の Compute@Edge は Wasmtime を中核のランタイムとし、すべての計算を Wasm へコンパイルすることを求めます。開発者はエッジのロジックを Rust、Go、あるいは JavaScript(Fastly が事前に Wasm へコンパイルします)で書き、できあがったモジュールが Fastly のエッジノードで動きます。このモデルは強い分離——要求ごとに状態を共有しない新しい Wasm インスタンスで実行されます——と、エッジ計算を実用にする起動速度の両方をもたらします。
エッジにおける Wasm の環境はまだ若いものの、急速に育っています。Fermyon Spin や Deislabs といった基盤は、サーバーレスの Wasm アプリケーションを作るために設計された枠組みを提供します。WASI ランタイムの細部を隠し、HTTP ハンドラ、キーバリューの保存、定期実行といった見慣れた型を用意しながら、実行はすべて Wasm を通します。その約束は、Wasm へコンパイルできるどの言語でも業務ロジックを一度書けば、WASI 互換のどの基盤にも変更なしで配備できる、というものです。
今日から Wasm を始めるには
始め方は目的によります。ブラウザで Wasm を使いたいなら、最短の道は Rust と wasm-pack です。Rust を入れ、wasm32-unknown-unknown ターゲットを加え、wasm-pack new でプロジェクトを作ります。生成されるプロジェクトには、JavaScript から呼べる Wasm 関数の動く例、npm 互換のパッケージを作るビルドスクリプト、そして画面のないブラウザでモジュールを走らせるテストの足場が含まれます。
サーバーサイドの Wasm を試したいなら、Wasmtime とそのコマンドライン道具から始めてください。wasmtime を入れ、WASI 対応で任意の C または Rust のプログラムをコンパイルし、wasmtime program.wasm で走らせます。Wasm へコンパイルされたコマンドラインのプログラムが、ネイティブの実行ファイルとまったく同じように動くのがわかるはずです——標準入力から読み、標準出力へ書き、ファイルに触れ、終了コードを返します。端末で Wasm のプログラムを動かす体験は、驚くほど滑らかです。
エッジとサーバーレスを試すなら、Cloudflare Workers が Wasm に対応した無料枠を用意しています。Rust で関数を書き、Wasm へコンパイルし、Cloudflare の全世界のネットワークへ配備してみてください。ほとんどの地域から 10 ミリ秒未満の応答時間が返り、Wasm がコールドスタートという心配ごとをいかに消し去るかを、身をもって知ることになります。
Wasm のもっとも実践的な第一歩は、アプリケーション全体を書き直すことではありません。計算の重い関数を一つ——フィルタ、変換、検証——見つけ、Rust で書き直し、Wasm へコンパイルし、いまのコードベースに組み込むことです。差を測ってください。五十行の Wasm で三倍の速さが出るのを目にしたとき、この技術がなぜ重要なのかがわかります。
道具立ては急速に良くなっています。wasm-pack、cargo-generate、wasm-tools が成熟した開発の流れを整えます。Binaryen のツールチェーンは wasm-opt でモジュールを最適化し、バイナリを 20 〜 30 パーセント小さくします。Wasmtime と Wasmer はネイティブ実行との差を詰め続けています。そしてコンポーネントモデルは、Wasm が登場した当初には不可能だった形で、モジュール同士を組み合わせられるようにするでしょう。
WebAssembly は、私たちの知るウェブを置き換えるものではありません。プラットフォームを豊かにするもの——JavaScript にできないことをする、二つ目のランタイムです。次に、JavaScript の動的な性質と格闘しているように感じる性能問題に出会ったら、計算の重い部分を Wasm モジュールへ切り出せないか考えてみてください。道具は本番に耐え、性能の差は本物で、Wasm の未来——コンポーネントモデル、WASI preview 2、より広い言語対応——は良くなる一方です。
ウェブプラットフォームは拡張できるように設計されました。WebAssembly はこの十年でもっとも重要なその拡張です。ウェブにできることの境を押し広げるアプリケーションを作りたい開発者にとって、これを理解することは選択肢ではありません。