Skip to content

필사 모드: React Compiler が Rust に移植された — マージされたもの、まだのもの、そしてその「10倍」という数字

日本語
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

はじめに — コンパイラはすでに1.0なのに、なぜまた移植するのか

React Compilerはすでに終わった話に見えました。React Compiler v1.0が2025年10月7日に出て、useMemouseCallbackを手で貼り付けていた時代を終わらせるという約束が、安定版のラベルを手にしたのですから。

ところが2026年6月9日、PR #36173 — [compiler] Port React Compiler to Rustfacebook/react mainにマージされました。コンパイラ全体をTypeScriptからRustへ書き直したものです。461ファイル、123,289行の追加。著者はReactチームのJoseph Savona(@josephsavona)です。

そして数日と経たないうちに、「React CompilerがRustで10倍速くなった」 という一文が生態系を駆け巡り始めました。本稿はこの一文を一次資料までさかのぼって検証する作業です。結論から言えば、マージされたことは事実であり、その数字は事実と呼びづらいものです — 著者本人がそう書き残しているからです。

何がマージされたか — まず事実関係から

PR本文の最初の一文がすべてを物語っています。

This is an experimental, work-in-progress port of React Compiler to Rust. (これはReact CompilerをRustへ移した、実験的かつ作業中の移植です。)

続く項目もトーンは同じです。

  • 作業中。 "we are sharing early, prior to testing internally at Meta, to get feedback from partners in parallel with continued development" — Meta内部でのテストに入るに、パートナーからのフィードバックを得るために早期公開したという意味です。
  • ビルドなし。 "No builds available yet, you'll have to do some hacking if you want to try this." 試すには自分で手を動かす必要があります。
  • 既知の穴はないが潜在バグはありうる。 "All fixtures pass, no known gaps but there may be lurking bugs."

タイムラインは2026年3月30日オープン、6月9日マージです。2か月半かかりました。

もう一点、指摘しておくべきことがあります — これが初めての試みではないということです。リポジトリの履歴を見ると、2025年1月24日に[compiler][be] Remove unused experimental Rust portというコミットがあります。Rustへの移植は一度試みられ、使われないまま削除されたことがあります。今回は二度目の挑戦です。

「3倍」と「10倍」— 著者本人が付けた警告文

さて、問題の数字です。PR本文で性能について書かれた項目全体をそのまま引くとこうなります。

Early performance numbers are derived from AI and i haven't spent much time validating the benchmark setup, beyond the fact that the optimization opportunities it discovered made complete sense and the fixes were right. With that caveat, it does appear that the Rust version is quite fast already: 3x faster when operating as a Babel plugin. The serialization cost is quite high, but the actual transformation logic is ~10x faster, so it's net faster. Native integrations (oxc, swc) should be even faster.

訳すと — 「初期の性能数値はAIが出したもので、ベンチマーク設定の検証にはあまり時間をかけていない。ただしAIが見つけた最適化の余地が完全に理にかなっていて、修正も正しかったという点までは確認した。その留保をつけたうえで言うと、Rust版はすでにかなり速いようだ — Babelプラグインとして動作させた場合3倍速い。シリアライズのコストはかなり高いが、実際の変換ロジック自体は約10倍速いので、差し引きでは速くなっている。ネイティブ統合(oxc、swc)はさらに速くなるはずだ。」

ここで正確に何が起きているかを分解してみましょう。

第一に、数字の出所はAIです。 著者がベンチマークを書いて動かして得たものではなく、AIが出した数値であり、著者は「ベンチマーク設定の検証にはあまり時間をかけていない」と明言しています。彼が検証したと言っているのは性能数値そのものではなく、AIが見つけた最適化の余地が妥当だったという点です。これはまったく別の主張です。

第二に、3倍と10倍は同じものを測った数字ではありません。 3倍はBabelプラグインとして動作するエンドツーエンドの数値であり、10倍はシリアライズのコストを除いた変換ロジックのみの数値です。「10倍速くなった」と訳した瞬間にシリアライズのコストが消えますが、著者は同じ文の中でそのコストは「かなり高い(quite high)」と述べています。実際にあなたのビルドに届く数字は10倍側ではなく3倍側です。

第三に、oxc/swcの方が速いというのは測定ではなく予想です。 原文は"should be even faster" — 助動詞が付いています。これを「もっと速い」と断定形で訳してはいけません。

第四に、その3倍すら、すでに古くなっています。 これはやや皮肉な話で、後段で見ていきます。

まとめると、これらの数字は実験的なWIPブランチの上で、AIが算出し、著者が検証していないと明言した数値です。引用できないという意味ではなく、引用するならその条件も一緒に持ち運ぶ必要があるという意味です。この生態系は、条件を外した数字を民間伝承に変える才能に長けています。

「アーキテクチャは人間が、コードは大半をAIが」

PRの中で数字と同じくらい話題になった項目がこれです。

The architecture was heavily guided by humans (me, @josephsavona) but majority coded by AI.

「大半をAIがコーディングした」だけを切り取ると誤解が生まれます。すぐ次の文が範囲を絞り込んでくれます。

I was very hands-on in setting the architecture, the testing and verification strategy, incremental migration approach, etc. I also kept a close eye on the code and spent a decent amount of time going back and forth to get code quality to a decent level.

つまり人間が握っていたのはアーキテクチャ、テスト・検証戦略、段階的な移行方針であり、コード品質についても「何度もやり取りを重ねながら」引き上げたとしています。AIが埋めたのは、その枠の中の実装です。

そして、この作業がAIにとって有利な条件だったという点も見ておく必要があります。これは白紙からコンパイラを設計したのではなく、答え合わせの対象がすでにある移植です。TypeScript実装がリファレンスとして存在し、パス単位で1対1対応がとれ(著者の言葉では"It's very much a pass-by-pass port")、出力が正しいかを機械的に照合できます。仕様がすでに実行可能な形で存在していた問題だったのです。ここでの結果を「AIがコンパイラを書ける」に一般化するのは行き過ぎです。

本当に見るべきは速度ではなく検証の方法

個人的にこのPRで一番学びが多い部分は10倍ではなく、ここです。答え合わせのある移植をAIにやらせるとき、どうやって信頼を作るか。

第1段階 — 出力の照合。

all 1725 fixtures pass in snap when comparing the temporary rust version of the plugin with the main version. this compares generated code output as well as errors.

1,725件のフィクスチャについて、Rust版と既存版の生成コードとエラーを照合します。ここまでは、よくあるゴールデンテストです。

第2段階 — パス単位の内部状態照合。 これが核心です。

all fixtures also pass a full comparison of the per-pass compiler intermediate representation — the intermediate state (including log events and errors) are ~identical after every single pass (modulo some normalization of ids)

最終出力だけを合わせれば「たまたま同じ答え」も通ってしまいます。そこですべてのパスが終わるたびに中間表現(IR)全体を、ログイベントやエラーまで含めて照合します。著者がこの目的のために作ったスクリプトがcompiler/script/test-rust-port.shで、PRの説明によれば任意のJSディレクトリに向けて実行できるため、Meta内部のテストに使う計画だとされています。

教訓はこうまとめられます — AIに大規模な移植をやらせるときに作るべきものは、コードではなく照合装置です。「出力が同じ」ではなく「すべての中間段階が同じ」を機械が確認できてこそ、12万行を人間がすべて読まなくても信頼できます。著者が「hands-on」だったと言う対象に「testing and verification strategy」が入っているのは偶然ではありません。

ただし、ここにも正直な穴があります。

The OXC and SWC example integrations seem to be working well, though i haven't manually verified this to the same extent as i have the Babel integration.

Babel統合ほどには手作業で検証していないとしています。

アーキテクチャ — 同じアルゴリズム、異なるメモリモデル

内部構造は意外に保守的です。PRの説明そのままに、Rust版はTypeScript版と同じアーキテクチャです。

AST  ->  HIR (High-level Intermediate Representation)
           - CFG (control-flow graph)
           - SSA (single-static assignment)
         |
         +-> 同じパス群、同じアルゴリズム
         |
         v
        出力AST

リポジトリのcompiler/cratesを開くと、この構造がそのままクレートに分かれています — react_compiler_hirreact_compiler_ssareact_compiler_loweringreact_compiler_optimizationreact_compiler_reactive_scopesreact_compiler_typeinferencereact_compiler_validationなどです。

変わったのはアルゴリズムではなくデータ表現です。

The main differences are in the data representation - using arena-like structures (and indices into these arenas) to work within Rust's borrowing system.

TypeScriptでオブジェクト参照によって組み上げていたグラフを、Rustではそのまま持ち込めません。借用チェッカー(borrow checker)が循環参照と可変エイリアスを許さないからです。そこでノードをアレナにまとめて入れ、インデックスで参照します。CFGやSSAのようにノードが互いを指し示す構造をRustへ移すとき、事実上の標準になったパターンです。

公開APIも独特です。

The public API is basically "Rust Babel AST" + Scope Info in, Rust Babel AST out.

Babel ASTのRust表現を共有インターフェースとして、各統合(Babel、OXC、SWC)が自分の表現へ変換して出し入れします。理由は現実的です — "the conversion from any AST into our HIR is complex, and we can only maintain one version"(どのASTであれHIRへ変換するのは複雑で、維持できるのは一系統だけだ)。さらに現状はスコープ情報を統合側でシリアライズして渡す必要があります。TypeScript版がBabelのスコープ解析に乗っていたためです。独自のスコープ解決を実装する計画だと明かされています。

ここで先ほどの「シリアライズのコストがかなり高い」が説明されます。Babel ASTをRustへ渡して受け取る往復に加え、スコープグラフまでシリアライズするコストが、変換ロジックの10倍という利得を3倍まで削っていたのです。

マージ後1か月 — 計画が変わった場所、まだ漏れている場所

ここから先は、PR本文だけ読んでいると見落とす部分です。コミット履歴のほうがはるかに正直です。

シリアライズコストにはすぐ手が入り始めました。 マージ2日後の6月11日に[rust-compiler] Return the compiled AST by value instead of JSON(#36729)と[rust-compiler] Carry uninspected AST subtrees as raw JSON text(#36730)が入りました。コンパイル済みASTをJSONではなく値として返し、中身を見る必要のないASTのサブツリーは生のJSONテキストとしてそのまま持ち運びます。先ほど「3倍がすでに古い」と言ったのはこの話です — その数字を作ったコードはマージ直後から変わり始めています。今の倍率が何倍かは誰も公開していません。

OXC/SWCの計画はひっくり返りました。 PR本文は「私たちのリポジトリに統合担当ごとのクレートを一つずつ(react_compiler_swc、react_compiler_oxc)置き、ロジックの大半がそこに住む」という絵を描いていました。ところが6月11日、[compiler] Remove OXC and SWC plugins in favor of them being handled by those projects(#36743)がマージされます。理由は一行です — "I believe the consensus is that the OXC and SWC plugins should live in those projects, and instead consume the React compiler as a crate"(OXC/SWCプラグインはそちらのプロジェクトに置き、React Compilerをクレートとして消費するのが合意のようだ)。実際、今のcompiler/cratesにはoxc/swcのクレートはありません。マージからわずか2日で統合戦略が変わったわけで、PR本文だけを読むと誤った絵を持ってしまいます。

Rustへ移すことで新たに生まれた問題もあります。 6月16日の[rust-compiler] Represent string values as JsString (WTF-16 aware)(#36731)。JavaScriptの文字列はWTF-16で、RustのStringはUTF-8です。サロゲートペアが不揃いな文字列をそのままRustの文字列として受け取ると、元の値を無損失で保持できません。TypeScriptには存在しなかった種類のバグが、言語を変えることでついてきたのです。6月22日にはUse rustc-hash FxHasher for all maps and sets(#36811)のような典型的なRust性能チューニングも入りました。

「すべてのフィクスチャが通過」には注釈が必要です。 マージコミットと一緒に入ったcompiler/crates/TODO.mdには、e2eパリティのスナップショット表があります。

Variant | Score       | Failures
Babel   | 1792 / 1802 | 10
SWC     | 1786 / 1802 | 16
OXC     | 1704 / 1795 | 91

これはPR本文の「1725件のフィクスチャ全通過」と矛盾しません — 別のスイートです。前者はsnapスイート(Rustプラグイン対既存プラグインの出力照合)で、この表は3つの統合バリアントをTypeScript実装と突き合わせたe2eパリティスイートです。要点は、統合ごとに見ればまだ全通過ではないということで、ドキュメント自身が"the OXC row predates the fixtures and has not been re-measured"(OXCの行はフィクスチャより前に測定されたもので、再計測していない)と書き残しています。参考までにcargo test --workspaceは84件通過0件失敗です。このファイルは6月9日のマージ以降手が入っていないため、現在の数値は異なる可能性があります。

そして今も穴を拾い続けています。 7月8日の[compiler] Bail out with Todo on using and await using declarations(#36946) — using / await using宣言に出会うと処理を諦めるようにしました。7月7日の[compiler] Port JSX tag classification fix to Rust (not-lowercase is a component)(#36951)のように、TypeScript側の修正をRustへ移すコミットも入り続けています。ここに、この移植の本当のコストが見えます — 当面のあいだ、二系統を同時に維持し続けなければなりません。

今使えるのか — npmが答える

この問いは検索より、コマンドで答えたほうが速いです。

npm view babel-plugin-react-compiler dist-tags
# latest: 1.0.0
# experimental: 0.0.0-experimental-a1856f3-20260507

2026年7月16日時点でlatestは依然として1.0.0(2025年10月)であり、最後のpublishは2026年5月8日の実験ビルドです。つまり、6月9日にマージされたRust移植を含むnpmビルドはまだ存在しません。 PRの"No builds available yet"は今も有効です。

裏取りもできます。OXCのReact Compilerドキュメントは、この機能を「experimental and under active development」と表示し「Options and behaviour may change」と警告しつつ、Reactにマージされたが未公開(unpublished)のRust移植をoxc-project/forked-react-compilerにリリース可能なクレートとしてベンダリングして使っていると明かしています。上流がpublishしないので、フォークして使っているわけです。

そしてreact.devのブログには、この移植についての記事がありません。現時点で最新の記事は2026年2月24日のThe React Foundationです。公式発表なしに静かにマージされた変更です。

この箇所で、民間伝承がどう作られるかをリアルタイムで見ることができます。検索すると「Next.js 16.4がRustコンパイラを搭載し、ビルドが40%以上速くなった」といった文が出てきます。確認するとこうです。

npm view next dist-tags
# latest: 16.2.10
# preview: 16.3.0-preview.6

Next.js 16.4は存在しません。 40%という数字にも出典がありません。存在しないバージョンの存在しないベンチマークが、すでに引用可能な文の形をして出回っているのです。フレームワークの性能数字を見るときは、npm viewの一行が検索結果10個より優れています。

コンパイラが今も嚙む場所 — メモ化の安全性

RustかTypeScriptかは、コンパイラがどこで嚙むかを変えません。 同じアルゴリズムの移植だからです。だから実務者にとって重要なのはむしろこちらです。

React Compilerは自動メモ化が安全でないAPIを把握しており、そうしたモジュールは最適化から除外します。そのリストはDefaultModuleTypeProviderにあり、2026年7月現在ちょうど3つです。

  • react-hook-formuseForm()が返すもののうちwatch()のみ
  • @tanstack/react-table
  • @tanstack/react-virtual

理由はソースコメントがよく説明しています。問題は内部可変性(interior mutability)です。新しい値を作って状態へ入れる代わりに値をその場で変異させるAPIは、関数オブジェクトが同一でも呼び出し結果が変わります。Reactは「中身が変われば外側のオブジェクトも変わる」という前提でメモ化するため、この前提が崩れます。コメントはこの状況をこう整理しています — Rules of Reactが精密に文書化される前に作られたライブラリ群が、メモ化に対して安全でないAPIをうっかり世に出してしまったのであり、開発者がそれを手動でメモ化するとアプリが壊れるか、コンパイラを試してみて「コンパイラが自分のコードを壊した」と考えることになる、と。

これが抽象的な懸念ではないことを示す出来事が10日前にありました。7月6日の[compiler]: add useWindowVirtualizer to known incompatible librariesです。2025年9月に@tanstack/react-virtualuseVirtualizerはリストに載せていたのに、useWindowVirtualizerを漏らしていたのです。両者は同じ内部実装を包む薄いラッパーなので、同じ問題を抱えます。結果は具体的です — 報告者のアプリでgetVirtualItems()初回レンダー時の値(計測前の空配列)に固まってしまい、仮想リストが永久に空のままになりました。誤ったメモ化のまま静かにコンパイルされていたのです。

ここから2つ読み取るべきことがあります。

第一に、この安全網はハードコードされたリストです。 既知のモジュールしか止めません。リストに載っていないのに同じパターンを使うライブラリ(あるいは社内のカスタムフック)は、そのまま通過します。OXCのドキュメントも同じことを言っています — "Code that breaks the Rules of React is also skipped rather than optimized — for example interior mutability, or libraries built on observable mutation such as MobX's observer()." MobXのobserver()が例として挙げられています。

第二に、これでリストを2か所直す必要が出てきました。 #36912は修正をDefaultModuleTypeProvider.tsとRustクレート側のdefault_module_type_provider.rs両方に入れました。先ほど述べた二重維持のコストが、まさにこういう形で現れます。

余談ですが、OXCのドキュメントには統合順序の落とし穴も書かれています — "The React Compiler requires the original source: it must see JSX before any other transform." emotionのCSS propやBabelのconstant-elementsのようにJSXを先に触るプラグインがあると壊れます。

Metaの12%と2.5倍は何を測った数字か

Rustの話とよく混同される数字があるので、切り分けておきます。React Compiler v1.0の発表にある文です。

We've seen initial loads and cross-page navigations improve by up to 12%, while certain interactions are more than 2.5× faster.

条件を正確に付けるとこうなります。

  • 誰が: Reactチーム(Meta)が自社アプリで報告した数値です。独立系のベンチマークではありません。
  • 何を: 初期ロードとページ間遷移が最大(up to) 12%改善、特定(certain) のインタラクションが2.5倍以上。"up to"と"certain"は飾りではなく範囲を限定する語です。平均ではありません。
  • いつ: 2025年10月、TypeScript版1.0の時点です。Rust移植とは無関係です。
  • メモリ使用量は中立だったと付け加えられています。

そして発表文自身が"Although your mileage may vary, we recommend experimenting with the compiler in your app to see similar performance gains"と書いています。ここで12%はランタイム性能であり、3倍/10倍はビルドタイムのコンパイル速度だという点も混同しやすいところです。軸そのものが違う数字です。混ぜて「コンパイラが10倍速くなったのでアプリが12%速くなる」といった文を作ると、二重に間違えることになります。

おわりに — 今あなたがすべきこと

まとめるとこうです。

事実であること: React CompilerのRust移植は2026年6月9日にマージされました。アーキテクチャはTypeScript版と同一(HIR + CFG + SSA、パス単位での移植)で、違いは主にアレナ・インデックス方式のデータ表現です。アーキテクチャと検証戦略は人間が握り、実装は大半をAIが書き、パス単位のIR照合というかなり堅牢な検証装置を備えています。

事実と呼びづらいこと: 「10倍」。AIが算出し、著者がベンチマーク設定を検証していないと明言した数値であり、しかもエンドツーエンドでは3倍であり、その3倍を作ったコードはマージ直後からシリアライズ経路が変わり、すでに古くなっています。「Next.js 16.4の40%」のたぐいは、そもそも存在しないバージョンの話です。

あなたにとって変わること: 今日時点ではありません。npmのlatestは1.0.0で、Rustビルドは公開されていません。今React Compilerを使っているなら、それはそのままTypeScript版1.0を使っているということで、それが正常な状態です。準備と呼べるものがあるとすれば、v1.0発表が勧めていたとおり正確なバージョンに固定(^1.0.0ではなく1.0.0)し、e2eテストを整えておくことくらいです。メモ化のかかり方はバージョンごとに変わりうるというのがその推奨の理由であり、実装がまるごと入れ替わる過渡期にはなおさらです。

次にいつ見るか: npm view babel-plugin-react-compiler dist-tagsが答えてくれます。Rustビルドがpublishされ、react.devに公式記事が上がり、crates/TODO.mdのパリティ表が整理される時点です。それまでは、ベンチマークのツイートではなくリポジトリが状態を語ります。

最後に、この出来事から一番長く残る教訓は性能ではなく方法論のほうにありそうです。12万行のコンパイラをAIで移植する中で信頼を作ったのは、コードレビューではなくすべてのパスの中間表現を機械的に照合する装置でした。答え合わせのある問題だったからこそ可能だったという限界も、あわせて覚えておく価値があります。そして、そのPRを書いた本人でさえ自分の数字に「検証していない」と注釈を付けていたのに、生態系はその注釈を外して引用した — こちらのほうが、もっと長く繰り返される教訓なのかもしれません。

参考資料

현재 단락 (1/99)

React Compilerはすでに終わった話に見えました。[React Compiler v1.0](https://react.dev/blog/2025/10/07/react-compiler...

작성 글자: 0원문 글자: 13,940작성 단락: 0/99