JavaとRust

JavaとRust、AIとの関係――「どちらがAIに向くか」は数字で答えられない

議論参加:梧桐 (編集長・進行) / 灰島 徹 (基幹系アーキテクト) / 桐生 みなも (Rustを本番運用するSRE) / 汐見 レン (証券システムの低レイテンシ実装者)

第5回は、どちらがどこで使われているかを一次情報だけで確かめた。今回は、この連載を始めるきっかけそのものを扱う。AIにコードを書かせるようになって、言語選択はどう変わったのか。

先に、この回で最も重要なことを書いておく。

RustとJavaのどちらがAI生成に向いているか」を、公開されているベンチマークの数字で断定できる状態にはない。

そして、そもそも「AI時代の言語選択」は三つの別々の問いであり、三つとも答えが違う。

まず、問いを三つに分ける

議論の出発点がここだった。「AI時代にはどの言語か」と一括で問うと答えが出ないのは、性質の違う問いが混ざっているからである。

問い答えの方向
(a)AIシステムを作る言語勝敗が存在しない。層が違う
(b)AIに書かせる言語条件によって逆転する
(c)AIが書いたものを人間が保守する言語組織の蓄積で決まる

同じ言語ペアで結論が三回ひっくり返る。順に見ていく。

(a) AIシステムを作る言語――競合していない

これは第5回で確認したとおりである。RustとJavaは、AIスタックの別の層にいる。

モデルそのものを定義し学習させる層は、Pythonが支配的である。RustもJavaも、そこにはいない。

Rustが入っているのはモデルの周りのインフラ――文字列の分割、列指向のデータ処理、ベクタ検索、Pythonのツールチェーンといった「大量のデータを速く回す」層である。Javaが強いのはAIに食わせるデータの上流と、AIの出力を業務システムへ接続する下流である。

つまりこの問いに関しては、両者は競合しておらず、補完的である。「RustかJavaか」という設定自体が成立しない。

(b) AIに書かせる言語――数字はある。だが答えにならない

いちばん知りたいのはここだろう。同じ仕様をAIに渡したとき、どちらの言語のほうがうまく書けるのか。

結論から言うと、数字はある。だが答えにはならない。 理由は精神論ではなく、ベンチマークの作られ方にある。

まず、実際の数字を見てもらう

前置きを並べるより、公開されている数字を出したほうが早い。SWE-bench Multilingual は、9言語・42リポジトリから集めた300件の課題をAIに解かせるベンチマークで、言語別の解決率を公開している数少ない一つである。課題は実在のGitHub issueと、それを直した実際の修正から作られている。

以下は同ベンチマークが公表している基準値(SWE-agent と Claude 3.7 Sonnet、1件あたり2.50ドルの費用上限)である。

言語解決全体解決率
Rust254358.14%
Java234353.49%
PHP214348.84%
Ruby194443.18%
JavaScript / TypeScript154334.88%
Go134230.95%
C / C++124228.57%
全体12830042.67%

Rustが1位、Javaが2位である。 この表だけを見れば「AIに書かせるならRust」と読みたくなる。

ここで、率ではなく件数を見てほしい。Rustは43問中25問、Javaは43問中23問。差は2問である。

その2問を、どう読むか

ここが今回いちばん慎重に扱った箇所である。

課題は言語ごとに別のリポジトリの別のissueから作られている。RustのタスクとJavaのタスクは、同じ問題の翻訳ではない。だから「Rustのほうが5ポイント高い」は、言語の差なのか、たまたま集まった課題の難易度の差なのかを、この数字だけでは分けられない。

重要なのは、ベンチマークの作成者自身がこの点を検証していることである。難易度の代理指標として「模範解答が何行書き換えているか」を使い、次のように書いている。

言語ごとの難易度の分布は、解決率と明らかな相関を示していない。たとえばRustの課題は模範解答の修正行数が平均して多いにもかかわらず、解決率は最も高かった

つまり「Rustのタスクが簡単だっただけ」という単純な説明は、作成者自身の分析では支持されていない。そのうえで作成者はこう結んでいる。

この限られた分析からは、解決率を決める要因のうち何が最も重要かを言うのは難しい。言語と難易度の両方が効いていそうだ、というところである。

編集部の立場も、ここと同じにする。 「言語の差ではない」と断定することもできないし、「Rustのほうが書かせやすい」と断定することもできない。2問差から、どちらの結論も出ない。

他のベンチマークはどうか

同じ観点で他も確認した。

ベンチマーク中身言語別スコア課題の等価性
SWE-bench Multilingual9言語・42リポジトリ・300課題。実在のissueとその修正公開している言語ごとに別リポジトリ・別課題
Multi-SWE-bench同じく実issueベース。課題数はより多い公開している非等価
MultiPL-E 系既存の小問を複数言語へ機械的に翻訳したもの集計は別途同一問題の翻訳なので等価
Aider polyglotExercismの演習225問を6言語(C++・Go・Java・JavaScript・Python・Rust)で公開していない(総合スコアのみ)最終更新は2025年11月20日

言語別スコアが出るものは課題が揃っておらず、課題が揃っているMultiPL-Eは小さな関数単位の問題に限られる。 実務のリポジトリ規模で、かつ言語間で等価な課題を持つベンチマークを、編集部は確認できなかった。

だから、「あのベンチではRustが58%でJavaが53%だった」という一文だけを持ち出す比較は、その5ポイントが何に由来するかを飛ばしている。 数字が無いのではない。数字はある。その数字が何を測っているかを、まだ誰も分けられていないというのが正確なところである。

梧桐(編集長・進行)

ここは編集部として一番慎重になった箇所です。数字を伏せるほうが安全ではありました。でも、伏せると「都合の悪い数字を隠した」のと区別がつかない。数字は出す。そのうえで、その数字から何が言えて何が言えないかを一緒に書く。 それが今回の判断です。

数字がないなら、何が言えるのか

構造的な性質としてなら、対称に整理できる。ただしこれは仮説であって、検証された事実ではない

Java側に働く力。学習データの量が桁違いに多く、定型的なコードが多い言語はAIが得意とする領域である。ただし「データが多い」と「データが新しい」は別で、世代が混ざったコードが害になるという逆風がある。古いイディオムと新しいイディオムが混在した状態で学習されていれば、生成されるコードも混ざる。

Rust側に働く力。コンパイラが機械可読で、しばしば修正案付きのエラーを返す。AIが生成→ビルド→エラー→修正という反復ループを回すとき、この信号の濃さが効く。第1回で見たとおり、Rustのコンパイラは「所有する必要がないなら借用に変えては」とまで書いてくる。

Rust側の逆風所有権の設計を間違えると、修正が非局所的になる。一箇所直しても直らず、構造ごと変える必要がある。この場合、エージェントが同じ二、三案の間を行き来して収束しないことがある。

コンパイラの射程には、限界がある

そしてここが、この回の核心である。

本誌は以前、同じ仕様書でAIに二回、Rustでクラッシュ安全なキーバリューストアを作らせる実験を行った。結果はこうだった。

両方ともビルドが通り、両方ともテストが通った。 そして両方とも、比較対象としたデータベースの約2倍の性能を出した。

ところが、どちらも測っていなかった項目があった。複数の書き手が同時に書き込もうとしたときの挙動である。編集部が測り直したところ、成功したコミット2,000件に対する再試行回数が、片方は50,021回、もう片方は0.12回だった。200倍の差である。

この差は、コンパイラが一切検出しなかった。両方とも安全なRustであり、両方とも型検査を通り、両方ともテストを通っている。差が出たのは、並行制御の設計判断という、型の外側にある領域だった。

同じことを、最小の形で再現する

言葉だけでは伝わりにくいので、同じ構図を短いコードに落として実際に動かした。共有する数値を、8スレッドから合計2000回ぶん更新する。更新のたびに少しだけ計算をする。

設計Aは楽観的に進める。いまの値を読み、計算し、その間に他のスレッドが書き換えていなければ書き込む。書き換えられていたらやり直す。

loop {
    let cur = cell.load(Ordering::Acquire);          // いまの値を読む
    let next = work(cur);                            // 時間のかかる計算
    if cell.compare_exchange(cur, next,              // 変わっていなければ書く
                             Ordering::AcqRel, Ordering::Acquire).is_ok() {
        break;
    }
    retries.fetch_add(1, Ordering::Relaxed);         // 駄目だった=やり直し
}

設計Bは悲観的に進める。先に鍵を取ってから計算する。第4回で見た Mutex である。

let mut g = cell.lock().unwrap();                    // 先に鍵を取る
*g = work(*g);                                       // 計算はその中でやる

compare_exchange は「いまの値が cur のままなら next に差し替える」という操作で、Javaの AtomicInteger#compareAndSet にあたる。Ordering:: で始まる指定は、この読み書きを他のスレッドからどう見えるようにするかの指定である(Javaでは volatileAtomic* クラスが同じ役割を担い、既定で最も強い保証が付く)。

3回続けて実行した結果である。

設計A(楽観的): 結果 2000 / やり直し 5628 回 / 10 ms
設計A(楽観的): 結果 2000 / やり直し 6887 回 /  9 ms
設計A(楽観的): 結果 2000 / やり直し 6992 回 /  8 ms
設計B(悲観的): 結果 2000 / やり直し    0 回 / 11 ms

両方ともコンパイルが通り、両方とも警告が出ず、両方とも答えは 2000 で正しい。 「結果が正しいこと」を確かめるテストは、どちらも通る。

それでも設計Aは、2000回の成功のために6千回前後を捨てている。設計Bは0回である。

ここで正直に書いておくと、この規模では所要時間にほとんど差が出なかった(Aが8〜10ミリ秒、Bが11〜12ミリ秒)。捨てた仕事は、他のCPUコアが空いているうちは表に出てこない。負荷が上がったときに初めて効いてくる種類の差であり、だからこそ手元のテストでは見えない。

この節の要点はそこにある。コンパイラは何も言わない。テストも通る。実行時間も変わらない。 「やり直した回数」という、それ用に数えたひとつの指標だけが差を示した。

汐見レン(証券システムの低レイテンシ実装者)

現場で怖いのは、この種の差が負荷の低いうちは完全に隠れることです。試験環境では出ない。本番でも平常時は出ない。混み合った瞬間にだけ、やり直しが跳ね上がって応答時間の裾が伸びる。測る指標を先に決めておかないと、そもそも見えません。 AIに書かせるかどうかとは別に、これは昔からある問題です。

桐生みなも(Rustを本番運用するSRE)

コンパイラが強いことと、設計が正しいことは別だという話が、この実験ではっきり出ました。しかも重要なのは、AIの反復ループを回してもここは直らないということです。コンパイラが何も言わないので、直すべきだという信号がそもそも発生しない。

第4回で見た「Rustが防ぐバグと防がないバグ」の線引きが、AI生成の文脈でそのまま効いている。コンパイラのフィードバックが濃いことは、設計レベルの誤りを捕まえることを意味しない。

(c) AIが書いたものを、人間が保守する言語

三つ目の問いである。ここは組織の話になる。

議論では「安全」に二種類あるという整理が出た。第1回から通底しているテーマの再登場である。

  • 予防型(Rust):起きないようにする。誤りをコンパイル時に前倒しする
  • 復旧型(Java):起きた後に直せる。実行中の状態を取り出して解析する道具と、それを使い慣れた人材が揃っている

どちらが優れているかではなく、組織がどちらの文脈で人材と道具を蓄積してきたかが選択を規定する、というのが議論の結論だった。24時間の運用体制と障害対応の型がある組織にとって、復旧型の安全は極めて実効的である。

ただしRust側には逆説がある。レビューの一部を型に外注できるが、レビュアがRustを読めなければ外注できない。

「コンパイラの出力を、もう誰もレビューしていない」

ここで、この話題になると必ず出てくる議論を正面から扱っておきたい。

コンパイラが吐いたバイナリを読む人間は、もうどこにもいない。同じことで、AIが生成したコードも人間が読まなくてよいのではないか。仕様さえ明確なら、問題が起きたときにはAIが直せばよい。

この議論には、成り立つ部分がある。抽象度が上がると下の層を人が読まなくなるのは、実際に繰り返し起きてきたことである。誰もアセンブリを読まないし、JITが出力した機械語も読まない。本稿の後半で述べる「言語選択は仕様管理戦略の出力である」という結論も、方向としては同じところを向いている。

そのうえで、この類比がどこまで成り立つのかを三つに分けて検討した。

① コンパイラは、同じ入力から同じ出力を返す。

だから「ソースが正であって、バイナリは導出物にすぎない」が成り立つ。バイナリを捨てても、ソースから作り直せば同じものが戻る。AIの場合、同じ仕様を渡し直しても同じコードは出てこない。モデルの版が変われば、なおさら違うものが出る。つまり生成されたコードは導出物ではなく、それ自体が原本である。

② コンパイラの正しさは、すべての利用者で分担されている。

主要なコンパイラは何百万人が同じ経路で使う。だから不具合が見つかり、直り、次の版で全員に配られる。一件ごとに違うコードを書くAIには、この分担がまったく効かない。 出力はすべて一点物である。

③ コンパイラは意味を保存する。AIは意味を作る。

ここが決定的だと考えている。コンパイラの仕事は、書かれた意味を変えないことである。判断を足さない。AIは逆で、仕様が黙っている場所すべてで決定を下す。そしてそれこそが、AIを使う理由である。

本稿がここまでに挙げた二つの実例が、そのまま反例になっている。前節の実験で仕様が言ったのは「クラッシュ安全なキーバリューストア」までで、楽観的に進めるか悲観的に進めるかは何も言っていない。最小再現のほうも、仕様は「共有する数値を2000回更新する」だけだった。どちらも仕様は満たしており、どちらもテストを通り、それでも競合下の振る舞いは別物だった。

コンパイラの類比が成立するには、この選択が意味的に中立でなければならない。中立ではない。

「仕様が明確なら」を、裏返して読む

いちばん重要なのは、バイナリを読まなくてよい理由のほうである。

読まなくてよいのは、読む必要がないからではない。代わりに読むべき、もっと良いものがあるからである。ソースコードという、形式的で機械が検査できる仕様が。

日本語で書かれた仕様書は、その「もっと良いもの」ではない。機械が検査できないからである。

だから条件を裏返すと、こう言える。

仕様が実行可能な形――テスト、型、性質――で書かれている範囲でだけ、この類比は成立する。散文で書かれている範囲では成立しない。

そして「再試行の回数」のような性質は、そもそも散文の仕様書にすら書かれないことが多い。

「問題が起きたらAIが直せばよい」について

これも同じところで詰まる。修正には「何が壊れているか」という信号が要る。

誤りの種類気づかせてくれるものAIの反復ループ
型や借用の誤りコンパイラ回る
仕様に書いた振る舞いの誤りテスト回る
設計判断の誤り(並行制御の方式など)無いそもそも始まらない

前節で桐生が述べたとおりである。コンパイラが何も言わないので、直すべきだという信号がそもそも発生しない。「後でAIが直せる」は「誰かが壊れていることに気づける」を前提にしており、その気づく仕組みこそが、人間のレビューが担ってきたものである。

なお、制度の側から見ても補助線が引ける。第5回で確認したとおり、機能安全の分野で「出力を読まなくてよい」を成り立たせているのは認定されたツールチェーンである。そしてRustプロジェクト自身が、認証は製品とツールチェーンの単位で行われるものだと明記していた。同じ意味で認定されたAIは、現時点で存在しない。

灰島徹(基幹系アーキテクト)

この議論を頭から否定する気はありません。むしろ達成条件を示してくれていると読みました。「AIのコードを読まなくてよいか」ではなく、「自分たちの仕様のうち、何割が機械検査可能な形になっているか」。その割合が、そのまま読まなくてよい割合になる。うちの場合は、正直まだ低い。

仕様は、どこへ揮発するか

議論で最も鋭かったのが、この論点だった。

Rustの型シグネチャは、データの生存期間や構造的な制約を、仕様の断片としてコードに刻む。第2回で見たとおり、&&mut かという宣言そのものが「このメソッドは読むだけです」という約束になる。これは仕様の一部がコードに残るということでもある。

だが、型で捕まえられない仕様もある。どこからどこまでを一つの処理としてまとめるか、同じ要求が二度届いても結果が変わらないようにすべきか、どの順序で起きなければならないか――こうした「いつ・どういう状況で」に関わる制約は、型に入らない。そこはRustでもJavaと同じく、ドキュメントか暗黙知に揮発する。

そして議論は、揮発の速度が非対称だと指摘した。

Javaにおける仕様の揮発は、人の退職や異動の速度で進む。

AIがコードを高速に生成するとき、設計意図の文脈はセッション単位で消える。

5年後の改修で、過去のAIとの対話にあった設計意図を再構成できるのか。議論はこの問いを未確定のまま終えた。 実証されていないからである。

灰島徹(基幹系アーキテクト)

これまでは「あの人に聞けば分かる」が最後の砦でした。その人が辞めるまでは持つ。AIに書かせた部分については、その砦が最初から無い。誰にも聞けないコードが、これまでより速く積み上がる可能性があります。

書き換えの経済が変わった、という事実

もうひとつ、第1回で触れた事実をここで位置づけ直しておきたい。

Bunは53万5,496行のZigをRustへ書き換えた。従来、この規模の書き換えは少人数のチームで1年かかり、その間バグ修正も機能追加も止まる。だから「書き換えは悪手」というのが常識だった。

Bunはこれを、AIエージェントの約50のワークフローを11日間回すことで実行した。実装するAIと、批判的に検証するAIを別々の文脈に立て、後者には「このコードは間違っているという前提で探せ」と指示している。

つまり、「書き換えのコストが高すぎて選べない」という制約自体が動いた可能性がある。これは言語の優劣とは別の次元の変化で、技術選定の前提条件に関わる。

ただし念のため、同記事は開発元による自社事例の報告であり、Anthropicによる買収と同社モデルの使用を自ら開示している。一例をもって一般化はできない。

最上流の問い――言語選択は「出力」である

議論の最終提言が、この回でいちばん実務的な含意を持っている。

言語選択は、AI戦略の入力ではなく、組織の仕様管理戦略の出力であるべきだ。

先に言語を決めてしまうと、後から戦略を言語に合わせることになる。本末転倒である。

先に決めるべきなのは、誰が、どの形式で、仕様を書くのかという役割設計だという。仕様をテストとして書くのか、型として書くのか、文書として書くのか。それが決まれば、どの言語がその形式を支えられるかは自ずと絞られる。

なお、この文脈で議論はJava側のひとつの資産を挙げた。仕様を先に書いてから実装を埋めるという開発の型が、Javaのエコシステムには蓄積されている。 これは「AIに仕様を先に書かせ、実装を後から埋めさせる」という進め方と相性が良く、AI時代にむしろ資産になりうる、という指摘である。ただしこの点は議論内で十分な検証を受けておらず、部分的に未確定として記録しておく。

この回で断定していないこと

  • どちらの言語がAIに書かせやすいかは断定していない。本稿が引いたSWE-bench Multilingualの数字は特定のモデル・特定の実行条件での一度の測定であり、RustとJavaの差は43問中2問である。課題が言語ごとに別である以上、この差から言語の優劣は導けない。本稿が挙げた構造的な性質(学習データの量、コンパイラが返す情報の濃さ、修正が非局所的になること)も仮説であって、検証された事実ではない
  • 本誌の実験の200倍という数値は、特定の仕様・特定の二つのモデル・特定の測定条件での一例である。言語間の比較ではなく、コンパイラが設計レベルの誤りを捕まえないことの実例として挙げている。本稿の最小再現(やり直し約6千回対0回)も同じ位置づけで、楽観的な設計が悲観的な設計より劣ることを示すものではない。競合が少ない場面では楽観的な設計のほうが速く、どちらを選ぶかは負荷の見積もり次第である。
  • AIが書いたコードの設計意図を、5年後に再構成できるかは未実証である。議論でも結論が出なかった。
  • 「AIのコードは人間が読まなくてよい」という主張の是非は、本稿では決めていない。本稿が示したのは、コンパイラとの類比が成立する条件(生成の再現性、意味の保存、機械検査可能な仕様の存在)であって、その条件を満たす仕組みが将来現れない、という主張ではない。現時点で満たされていないというだけである。
  • 世代の混ざったコードを学習したAIが生成するJavaコードに対して、JVMの復旧型の安全性が従来どおり機能するかも未確定である。
  • 仕様先行のテスト文化がAI時代の資産になるという指摘は、議論内で十分な検証を経ていない。
  • Bunの事例は開発元による自社事例の報告であり、一例をもって書き換えのコストが一般に下がったとは言えない。

梧桐(編集長・進行)

この回の持ち帰りは、答えではなく問いの置き換えです。「JavaとRustのどちらがAIに向いているか」ではなく、「自分の組織の仕様は、どこに置けばAIと一緒に10年後も回収できるか」。 ベンチマークの2問差も、コンパイラとの類比も、行き着く先は同じ問いでした。次回は最終回として、両者のこれからを見ます。

出典を読むときのポイント

  • Bun「Rewriting Bun in Rust」:53万5,496行の書き換えを、AIエージェントの約50のワークフローで11日間実行したこと、実装役と批判的検証役のAIを別の文脈に立てたことを確認した。開発元による自社事例の報告であり、同記事自身がAnthropicによる買収と同社モデルの使用を開示している。
  • SWE-bench Multilingual:9言語・42リポジトリ・300課題という構成、本稿に転載した言語別解決率の表(Rust 25/43、Java 23/43 など)、基準値がSWE-agent と Claude 3.7 Sonnet によるものであることを確認した。あわせて、作成者自身が難易度の代理指標で検証し、「Rustの課題は模範解答の修正行数が平均して多いのに解決率は最も高い」「言語と難易度の両方が効いていそうだ」と書いていることを確認した。本稿の解釈はこの記述に沿っている。
  • MultiPL-E:既存のベンチマークを複数言語へ翻訳する仕組みで、同一問題の並行版である点を確認した。ただし課題は関数単位で小さく、実務のリポジトリ規模のタスクではない。
  • Aider polyglot leaderboard:複数言語を対象としながら言語別の内訳を公開していないこと(総合スコアのみ)を確認した。言語比較の根拠には使えない。
  • Android「メモリ安全性」:既存コードの全面的な書き換えが現実的でないという開発元の見解を確認した。第5回で詳しく扱っている。
  • Rust「Announcing async fn and return-position impl Trait in traits」:Rustのコンパイラが提示するフィードバックの性質を確認するために参照した。第4回で扱った制約の出典でもある。
  • 本誌の実験記事:「2.0倍」と置いたら未達、「1.5倍」と置いたら達成。同一仕様でAIにRustのキーバリューストアを作らせ、両者ともビルドとテストを通しながら、競合下の挙動に200倍の差が出た経緯を記録している。測定条件も同記事に記載している。

キーワード:#AIコーディング#ベンチマーク

出典

コメント