RUST × WEB ARCHITECTURE
Rust言語の秘密
バックエンド・WebAssembly・JavaScript内部実装
「RustはWebでも使われている」という説明は正しい。しかし、その一言だけでは、RustがサーバーでHTTPを処理しているのか、ブラウザ内で計算しているのか、JavaScriptツールの内部エンジンとして潜んでいるのかが分からない。本稿では、WebにおけるRustの3つの顔を、実行場所・データ境界・ビルド方式・向いている処理という技術軸で分解する。
前提:Rustは「Webの新しい標準言語」ではなく、重い境界を受け持つ言語である
Webアプリケーションは、ひとつの言語だけで動いているわけではない。ブラウザにはHTML、CSS、JavaScriptがあり、サーバーにはPHP、Node.js、Python、Java、Goなどがあり、さらにデータベース、画像処理、検索、ビルドツール、CDN、エッジ実行環境が連結している。Rustは、この巨大な配管をすべて交換するのではなく、速度、安全性、並行処理、メモリ使用量が問題になりやすい区間へ差し込まれることが多い。
Rustの採用判断は「WebサイトをRustで作るか」ではなく、「どの実行境界をRustへ渡すと利益が出るか」で考える。WebにおけるRustは、置換言語というより高性能な専門区画である。
サーバー
HTTP、認証、API、検索、集計、画像変換
Browser → HTTP → Axum → DB
ブラウザ
計算、変換、描画補助、ローカル検索
JavaScript ⇄ Wasm Module
開発ツール内部
構文解析、変換、圧縮、Lint、バンドル
npm command → Rust engine
顔1:バックエンドAPIとして動くRust
一つ目は最も理解しやすい。Rustをサーバー上のネイティブ実行ファイルとして動かし、HTTPリクエストを受け、データベースを読み、JSONを返す方式である。代表的な選択肢にはAxum、Actix Web、Rocketなどがあるが、ここでは現在のRust非同期エコシステムとの接続が分かりやすいAxumを中心に見る。
Axumは単体の巨大フレームワークではない
Axumは、HTTPの低層処理、非同期実行、ミドルウェアをすべて独自実装しているわけではない。概念的には次の部品が重なっている。
この構成の利点は、ルーティングだけを担当する層と、HTTPそのもの、非同期実行、横断的なミドルウェアが分離されている点にある。アプリケーションコードでは「URLからIDを取得する」「JSONを型へ変換する」「認証済みユーザーを受け取る」といった処理をExtractorとして宣言できる。入力値が期待する型に変換できなければ、ハンドラー本体へ入る前に拒否できる。
use axum::{
extract::{Path, State},
http::StatusCode,
routing::get,
Json, Router,
};
use serde::Serialize;
use std::sync::Arc;
#[derive(Clone)]
struct AppState {
ships: Arc<Vec<Ship>>,
}
#[derive(Clone, Serialize)]
struct Ship {
id: u32,
name: String,
ship_class: String,
}
async fn get_ship(
State(state): State<AppState>,
Path(id): Path<u32>,
) -> Result<Json<Ship>, StatusCode> {
state
.ships
.iter()
.find(|ship| ship.id == id)
.cloned()
.map(Json)
.ok_or(StatusCode::NOT_FOUND)
}
#[tokio::main]
async fn main() {
let state = AppState {
ships: Arc::new(vec![
Ship {
id: 1,
name: "雪風".into(),
ship_class: "陽炎型".into(),
},
]),
};
let app = Router::new()
.route("/api/ships/{id}", get(get_ship))
.with_state(state);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
.await
.unwrap();
axum::serve(listener, app).await.unwrap();
}
この短い例でも、パスのidは文字列ではなくu32として受け取られ、レスポンスはShip型からJSONへ変換される。Rustの型は単なる補完機能ではなく、HTTP境界で入力と出力の契約を組み立てる材料になる。
なぜ高負荷処理でRustが候補になるのか
Rustにはガベージコレクタによる全体停止がなく、所有権と借用規則によってメモリ寿命をコンパイル時に検証する。これは「必ず速い」という意味ではないが、レイテンシの振れ幅を抑えたい処理や、大量の小さなオブジェクトを扱う処理、画像変換、圧縮、全文検索、暗号化、解析などでは設計上の利点になりやすい。
また、非同期処理では、接続ごとにOSスレッドを増やすのではなく、多数のタスクを少数のワーカースレッド上で進められる。ただし、ここで重要なのは、asyncがCPU処理を速くする機能ではないことだ。非同期処理が強いのは、DB応答待ち、外部API待ち、ソケット待ちのようなI/O待機である。画像解析や巨大な並べ替えのようなCPU処理を非同期タスクへそのまま入れると、ランタイムのワーカーを塞ぐ。CPU負荷の高い処理は専用スレッドプールやジョブキューへ分離する必要がある。
async fnに書き換えるだけでは高速化しない。I/O待ちを効率よく重ねる仕組みであり、CPU処理の並列化とは別の問題である。
バックエンドRustが向いている条件
- APIの応答時間やメモリ使用量が事業上の問題になっている
- 画像・PDF・検索・圧縮・暗号などCPU負荷の高い処理がある
- 長時間動く常駐プロセスで、障害とメモリ破壊を減らしたい
- 型によるAPI契約を重視し、テスト可能な境界を作りたい
- 単一バイナリとしてVPSやコンテナへ配布したい
逆に、管理画面中心の小規模CRUD、WordPressで十分な情報サイト、頻繁に仕様が変わる試作では、PHPやNode.jsの方が開発速度で有利なことが多い。Rustを導入する価値は、実装言語の格好良さではなく、ボトルネックの金額と障害リスクで判断すべきである。
顔2:ブラウザ内で動くRust+WebAssembly
二つ目は、RustコードをWebAssembly、略してWasmへコンパイルし、ブラウザ内で実行する方式である。ここでRustはOS上のネイティブプロセスではない。ブラウザが用意したWebAssembly仮想命令環境の中で動く。
src/lib.rs→
wasm32-unknown-unknown→
.wasm+
wasm-bindgen→
ブラウザ向けRustでは、wasm32-unknown-unknownターゲットがよく使われる。このターゲットはホストOSの機能をほとんど仮定しないため、通常のRustプログラムで使える機能がそのまま使えるわけではない。たとえば、ブラウザ内のWasmからローカルファイルシステムへ自由にアクセスしたり、通常のstd::thread::spawnでOSスレッドを生成したりはできない。
JavaScriptとの境界にはwasm-bindgenが使われる。Rust関数をJavaScriptから呼び出せる形にし、文字列、配列、オブジェクト、例外などの受け渡しを補助する。ただし、RustとJavaScriptは同じオブジェクト表現を共有しているわけではない。大きな文字列や配列を境界越しに何度も渡すと、変換やコピーのコストが発生する。
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn score_ship(
speed: f64,
range: f64,
armament: f64,
) -> f64 {
speed * 0.30 + range * 0.25 + armament * 0.45
}
import init, { score_ship } from "./pkg/ship_score.js";
await init();
const score = score_ship(35.0, 5000.0, 8.0);
console.log(score);
数値だけを渡す例は境界コストが小さい。一方、何万件もの艦艇データを毎回JavaScriptオブジェクトからRust構造体へ変換し、結果をまた巨大なJSONで返す設計は、Wasm側の計算が速くても全体では遅くなることがある。Wasm最適化の基本は、呼び出し回数を減らし、まとまったデータを一度渡し、内部で複数処理を完了させることである。
DOM操作はRustだけで完結するのか
WebAssemblyはHTML要素を直接知っているわけではない。DOM、Canvas、WebGL、Fetch、StorageなどのブラウザAPIへ触れるには、JavaScriptとの橋渡し、またはweb-sysのようなバインディングを使う。したがって、Rustでブラウザアプリを書く場合も、ブラウザのイベントループ、DOM、CSS、ネットワークというWeb固有の仕組みを理解する必要がある。
Leptos、Yew、Dioxusなどのフレームワークは、この橋渡しを高い抽象度で扱う。特にLeptosは、クライアントレンダリングだけでなく、サーバーサイドレンダリング、ハイドレーション、サーバー関数まで含むフルスタック構成を提供する。ただし、フルスタックRustを採用すると、既存のJavaScript資産、npm中心のUI部品、フロントエンド人材との接続コストも生まれる。
Wasmが本当に強い処理
ローカル全文検索
インデックスをブラウザへ配布し、通信なしで検索する。静的ドキュメントやオフライン対応と相性がよい。
画像・音声・圧縮
フィルタ、変換、波形解析、デコードなど、まとまったCPU処理をブラウザ側へ移せる。
図面・地図・シミュレーション
座標変換、衝突判定、経路計算、ズーム時の再計算など、数値処理の比率が高い機能に向く。
既存Rustライブラリの再利用
サーバーやCLIで使っている解析ロジックを、条件が合えばブラウザ向けにも共有できる。
Wasmが向かない、または慎重に扱う処理
- フォーム中心で、処理の大半がDOM更新だけの一般的な画面
- 小さな計算を高頻度でJavaScriptと往復する設計
- 巨大なWasmファイルを初回表示で必ず読み込む構成
- アクセシビリティやSEOが重要なのに、初期HTMLを出さない構成
- ブラウザ互換性、Worker、SharedArrayBuffer、セキュリティヘッダーの運用を軽視した並列処理
Wasmは「JavaScriptより速い魔法の箱」ではない。計算密度が高く、境界越しのデータ移動が少なく、初期ロード増加を許容できる機能で効果が出る。UI全体をRustへ移す前に、純粋な計算関数を一つ切り出して計測する方が安全である。
顔3:JavaScriptツールの内部に隠れるRust
三つ目は、利用者がRustコードを書かない方式である。表面はnpmパッケージやNode.jsコマンドのまま、構文解析、変換、圧縮、Lint、バンドルなどの内部エンジンだけがRustで実装される。
SWCはRust製のJavaScript/TypeScriptコンパイラ基盤であり、BiomeはFormatterとLinterをRustで構築している。利用者はnpxや設定ファイルを使うだけで、所有権やライフタイムを意識しない。それでも、大量ファイルを解析する中核部分ではRustの並列処理やメモリ管理の恩恵を受けられる。
npm run build→
→
→
なぜ構文解析と変換はRustに向くのか
コンパイラやLinterは、文字列を一行ずつ読むだけではない。ソースコードをトークンへ分解し、ASTと呼ばれる構文木を作り、名前解決や規則判定を行い、別のコードへ再生成する。プロジェクト規模が大きくなるほど、ファイル数、ノード数、依存関係、キャッシュ管理が増える。
この領域では、データ構造の寿命が明確で、並列化しやすく、同じ処理を大量に繰り返す。Rustは、C/C++に近い制御性を持ちながら、解放後参照やデータ競合を型システムで抑えるため、長期運用される開発基盤の中核に採用しやすい。
Node.jsからRustを呼ぶ方法
実装方式は一つではない。代表的には次の二つがある。
| 方式 | 実行形態 | 長所 | 注意点 |
|---|---|---|---|
| ネイティブアドオン | OS向け機械語 | 高速でOS機能を使いやすい | Windows、macOS、Linux、CPUごとのビルドと配布が必要 |
| WebAssembly | Wasmランタイム上 | 配布しやすく、隔離性が高い | ホスト機能、スレッド、I/O、境界変換に制約がある |
ネイティブアドオンは最高性能を狙いやすいが、配布物の組み合わせが増える。たとえばWindows x64、Windows ARM64、macOS Intel、macOS Apple Silicon、Linux glibc、Linux muslなどを考慮する必要がある。npmパッケージとして自然に見せるには、CIで各環境向けバイナリを生成し、適切なファイルを選択する仕組みが必要になる。
つまり「内部をRustに書き換えれば終わり」ではない。ビルド、クロスコンパイル、署名、配布、互換性、クラッシュログ、デバッグ情報まで含めて初めて製品になる。この顔が最も静かに見えるのは、ツール開発者がその複雑さをパッケージ内部へ封じ込めているからである。
2026年時点のWASIとComponent Model:サーバーWasmはどこまで来たか
ブラウザ外でWasmを動かす仕組みとしてWASIがある。WASIは、ファイル、時刻、乱数、ネットワークなど、プログラムがホスト環境へ要求する機能を標準化しようとする。2024年のWASI 0.2ではComponent Modelが本格的に組み込まれ、2026年6月にはWASI 0.3が正式化され、非同期処理がWebAssembly Componentsの標準機能として前進した。
ここで期待されるのが、言語の異なる小さなコンポーネントを、明示的なインターフェースで接続する構成である。Rustで書いた検索エンジン、Goで書いたAPI、JavaScriptで書いた制御ロジックを、巨大なコンテナ単位ではなく、能力を制限したコンポーネントとして組み合わせる方向性が見えている。
WASI 0.3で非同期が標準化されたことと、既存のLinuxサーバーアプリケーションを無修正でWasmへ移せることは同じではない。ランタイム対応、ライブラリ対応、ソケット、データベースドライバ、監視、デバッグ、性能特性は依然として確認が必要である。
サーバーWasmの強みは、起動の軽さ、隔離、能力ベースの権限制御、同一ホスト上で多数の小さな処理を安全に実行しやすい点にある。エッジ関数、プラグイン、ユーザー定義処理、マルチテナント環境では魅力が大きい。一方、既存のネイティブライブラリを大量に使う業務システムや、DB接続を長時間維持するAPIサーバーでは、ネイティブRustやコンテナの方が単純な場合が多い。
3つの顔を、実行場所と境界で比較する
| 観点 | バックエンドRust | ブラウザWasm | JSツール内部Rust |
|---|---|---|---|
| 主な実行場所 | VPS、クラウド、コンテナ | Webブラウザ | 開発PC、CI、Node.jsプロセス |
| 入口 | HTTP、Queue、CLI | JavaScript、DOMイベント | CLI、npm API、Editor |
| 得意分野 | API、検索、変換、常駐処理 | 計算、描画補助、ローカル処理 | 解析、変換、Lint、Bundle |
| 最大の利点 | 予測しやすい性能と安全性 | サーバー通信なしで高密度計算 | 利用者にRustを意識させず高速化 |
| 主なコスト | 学習、ビルド時間、運用人材 | 境界変換、初期ロード、Web API連携 | クロスプラットフォーム配布 |
選定フロー
- 遅い場所を測る。CPU、I/O、ネットワーク、DB、DOM、ビルドのどこが支配的かを分ける。
- Rustへ渡す境界を一つに絞る。API一個、変換関数一個、検索インデックス一個から始める。
- 境界越しデータ量を測る。JSON変換やメモリコピーが計算時間を上回っていないか確認する。
- 運用コストまで比較する。ビルド、配布、監視、障害解析、更新手順を含めて判断する。
- 既存言語版とベンチマークする。Rust採用を目的化せず、実際のデータと実運用条件で比較する。
既存WordPressサイトへ導入するなら、どこから始めるべきか
戦史記事、艦艇データベース、図面、写真、検索を持つ既存WordPressサイトを、いきなりAxumやLeptosで全面再構築するのは合理的ではない。WordPressには記事管理、編集画面、SEO、カテゴリ、メディア管理、REST APIという成熟した資産がある。全面置換は、その資産を捨てて同じ機能を再実装する作業になる。
現実的なのは、WordPressを母艦として残し、重い処理だけを外部化する構成である。
Rust CLIを導入
VPS上で、画像メタデータ抽出、検索インデックス生成、Markdown検査、リンク切れ検査を行う。Web公開部分を変えずに導入でき、失敗時の影響範囲が小さい。
Axumの補助API
艦艇・作戦・人物の複合検索、関連度計算、図面変換、アクセス集計など、WordPress REST APIだけでは重い処理を別サービスへ切り出す。
ブラウザWasm
艦艇一覧の多条件フィルタ、作戦参加艦の時系列表示、図面の座標変換、航路シミュレーションなど、閲覧端末側で完結させたい計算を移す。
推奨構成
WordPress
├─ 記事・固定ページ・SEO・商品導線
├─ REST API
└─ JavaScript UI
├─ 通常のDOM操作
└─ 必要な箇所だけWasmを呼び出す
Rust Service
├─ Axum API
├─ SQLite / PostgreSQL
├─ 検索インデックス
├─ 画像・PDF変換
└─ Project G Coreの補助処理
この構成では、WordPressが読者に見える艦橋を担当し、Rustは機関室へ入る。画面、記事、商品導線は既存資産を利用し、検索や変換など負荷が集中する区画だけを強化できる。
最初の実証実験として適した機能
最初に作るなら「艦艇データの多条件フィルタ」がよい。入力は艦型、建造年、速力、兵装、参加作戦などに限定でき、出力もID配列や並べ替え結果に抑えられる。JavaScript版とRust/Wasm版を同じデータで比較し、データ件数が100件、1,000件、10,000件と増えたときの処理時間、初期ロード、メモリ量を測定する。
ここで差が出なければ、Wasmは導入しない。それも正しい結論である。Rustは使うこと自体が成果ではない。既存システムの安全性、速度、運用性を、必要な場所だけ改善できたときに初めて価値が生まれる。
結論:Rustの3つの顔は、同じ言語でも別の設計問題である
バックエンドRustは、HTTPとデータベースの間で動くネイティブサーバーである。ブラウザWasmは、JavaScriptと協調しながら計算密度の高い処理を引き受ける。JavaScriptツール内部のRustは、利用者から複雑さを隠し、構文解析や変換エンジンとして働く。
同じRustでも、実行環境、メモリ境界、配布方法、デバッグ手段、失敗時の影響はまったく異なる。だから「RustでWebを作る」という一文から技術選定を始めてはいけない。まず、どこで動かすのか。何を渡すのか。どの処理が重いのか。既存資産を残せるのか。この四点を決める。
WebにおけるRustの本質は全面置換ではない。既存システムの中で、性能と安全性が必要な区画だけを、静かに引き受けることにある。