JavaとRust

JavaとRust、これから――境界は動くが、消えはしない

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

連載の最終回である。所有権設計の語彙メモリ並行処理実績AIとの関係と見てきた。

最後に、両者のロードマップを並べる。ここでも予測はしない。公表されている計画と、公表されていないことを、そのまま並べる。

なお本稿ではJavaの機能提案(JEP)の進み具合を扱う。段階を先に整理しておく。

段階意味
Candidate提案として受理された。実装が始まる保証はない
Submitted対象バージョンへの採用を申請中
Targeted特定のバージョンに載ることが決まった
Integrated実装がそのバージョンの本流に入った
Deliveredリリース済み

「Integratedだから使える」ではない点に注意がいる。入ったのは開発中の版であって、手元のJDKに届くのはGAの後である。

Javaは値型でRustに近づく。ただしメモリ配置を書き手が決めるという壁は残る。

Rustは非同期の穴を埋めようとしている。ただしいま最も安全性保証が弱いのは、Rustが最も拡大したい領域と重なっている。

境界は動くが、消えはしない。

Java側――近づく部分と、依存で数珠つなぎになっている部分

そもそも、何が「近づく」のか

値クラスの話に入る前に、何の差を埋めようとしているのかを確認しておく。ここを飛ばすと、以降の依存関係が読めなくなる。

第3回で見たとおり、Javaのオブジェクトは常にヒープに置かれ、変数はそこへの参照を持つ。 だから点の配列を100万個作ると、配列に並ぶのは100万個の参照であって、点の実体は別の場所に散らばる。

Rustの Vec<Point> はそうならない。点の実体そのものが、隙間なく連続して並ぶ。

この差が効く理由は、CPUのメモリの読み方にある。CPUはメモリを1バイトずつではなく、まとまった塊で読み込む。実体が連続していれば、1回の読み込みで複数個ぶんが手に入る。散らばっていれば、1個読むたびに別の場所へ取りに行くことになる。数を扱う処理では、ここが効く。

値クラス(JEP 401)は、この差を埋めにいく提案である。 「この型のインスタンスは同一性を持たない」と宣言することで、JVMに実体を並べる自由を与える。同一性――== で「同じ物か」を判定できる性質――を捨てる代わりに、置き場所の自由を得る、という取引である。

ただし、制御権は渡されない

第1回で触れたとおり、値クラスはJDK 28の本流に実装が入った状態にある。JDK 28のGAは2027年3月の予定である。裏を返すと、2026年9月時点では、どのGA済みJDKにもプレビューとしてすら入っていない。

そしてJEP 401の原文は、期待を先回りして限定している。値をフラットに並べることは「最適化であって言語機能ではない。直接制御することはできない」。ジェネリクスは型消去のため対象外である。

ここが重要なところである。JVMは「並べてもよい」と許可されるだけで、「必ず並べる」とは約束していない。 書き手にできるのは、値クラスだと宣言して最適化の可能性を開くところまでで、実際に並ぶかどうかは実行基盤の判断に残る。

一方Rustでは、struct を定義した時点で並び方が決まっている。判断する主体が違う。

つまり方向としては近づくが、「並び方を書き手が決められる」という性質そのものは、設計として別のまま残る。 第1回から通底している「実行基盤に委ねるか、コードに書くか」の分岐が、ここでもそのまま出ている。

型消去そのものを解決する道筋はどうか。プリミティブ特殊化ジェネリクス(JEP 218)はCandidateのまま2017年から更新がない。9年動いていない。

依存で数珠つなぎになっている

議論が指摘した構造がここである。

Vector API(JEP 537)はJDK 27で12回目の試験導入段階にある。 そしてJEP 537自身が、Project Valhallaの必要な機能がプレビューとして使えるようになるまで試験導入を続けると明記している。

なぜ待つ必要があるのか。ここも順を追うと分かる。

Vector APIは、複数の数値をひとまとまりとして、CPUの1命令でまとめて計算するための仕組みである。効果が出るのは、そのまとまりがCPUの専用レジスタに乗っているあいだだけで、ヒープ上のオブジェクトとして作っては捨てるのでは、狙った速度にならない。

そして前節で見たとおり、いまのJavaのオブジェクトは同一性を持ち、ヒープに置かれる。JEP 537が挙げている長期目標は、このAPIのクラスを値クラスに変えることである。同一性を捨てられれば、置き場所の制約から外れる。

つまりValhallaが遅れれば、Vector APIもその分止まる。 数値計算をまとめて処理する機能が、値型の到着待ちで9年以上インキュベータにいるのは、この依存による。

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

値型の話は、待っている側からするとかなり長い列です。Vector APIが値型を待ち、値型がJDK 28を待ち、そのJDK 28が2027年3月。数を大量に回す処理でJavaを選ぶかどうかの判断は、この列の長さごと引き受けることになります。いま必要なら、いま並んでいるものは使えません。

構造化並行性(JEP 533)も、JDK 27で第7プレビューである。正式化を狙うJEPは申請中(Submitted)のままで、JDK 28に載ることも決まっていない

Java側の機能状態
値クラス(JEP 401)JDK 28 に実装が入った(Integrated)。GAは2027年3月予定
Vector API(JEP 537)12回目の試験導入。値型待ち
プリミティブ特殊化ジェネリクス(JEP 218)提案が受理されたまま(Candidate)2017年から更新なし
構造化並行性(JEP 533)第7プレビュー。JDK 28のターゲット外
Shenandoah世代別モードの既定化(JEP 535)JDK 28に載ることが決定(Targeted)

一方、既に出荷されている成果もある

期待だけを並べるのは公平ではない。起動時間の改善は、もう手元にある。

機能出荷済み
AOTクラスローディング&リンク(JEP 483)JDK 24
AOT機能のコマンドライン簡素化(JEP 514)JDK 25
AOTメソッドプロファイリング(JEP 515)JDK 25
任意のGCとのAOTオブジェクトキャッシュ(JEP 516)JDK 26

トレーニング実行の結果を再利用して起動を速くする仕組みは、すでに正式機能として使える。第1回で測った起動時間の差は、これらを使わない素の状態での比較だった。

ただしネイティブコードのAOTコンパイル(draft 8335368)は申請中のままで、まだどのバージョンにも入っていない。

Rust側――埋めようとしている穴と、その矛盾

Rustのロードマップは、言語自身が未整備を認めているという点で読みやすい。

借用検査の刷新。 次世代の借用検査(Polonius Alpha)が2026年8月4日にnightlyで有効化された。これまで通らなかった正しいコードが通るようになる、というものである。

抽象的なので、実際に何が変わるのかを見てもらう。以下は公式の発表記事が挙げている例とほぼ同じもので、マップにキーがあればその値を、無ければ作ってから返すという、ごく普通の関数である。

fn get_or_insert(map: &mut HashMap<String, String>, key: String) -> &mut String {
    match map.get_mut(&key) {
        Some(v) => v,                              // あればそれを返す
        None => {
            map.insert(key.clone(), String::new()); // 無ければ作って
            map.get_mut(&key).unwrap()              // それを返す
        }
    }
}

このコードは、現在の安定版(rustc 1.96.0)ではコンパイルが通らない。

error[E0499]: cannot borrow `*map` as mutable more than once at a time
 4 |     match map.get_mut(&key) {
   |           --- first mutable borrow occurs here
 7 |             map.insert(key.clone(), String::new());
   |             ^^^ second mutable borrow occurs here

第4回でも出た E0499――書き換えるための借用は同時に一つしか作れないである。だが、よく見てほしい。map.get_mut() が作った借用は、Some の側でしか使っていない。None の側では、その借用はもう要らない。

人間には自明だが、いまの検査はそこを区別しない。分岐ごとに借用が生きているかを見ておらず、関数全体で生きていると見なすからである。Polonius Alphaは、ここを分岐ごとに追えるようにする。公式の言葉では、この借用は「None の分岐では生きていない」と判定できるようになる。

つまりこれは、新しく書けることが増える話ではなく、いままで書けたはずのものが書けるようになる話である。第1回で「Rustの学習コストの一部は、コンパイラの表現力不足に由来する」と書いた、その部分が削られていく。

ただし公式が正直に書いていることが二つある。ひとつはコンパイル時間の悪化で、利用の多い上位1万クレートではおおむね軽微だったものの、その範囲外で最悪2〜3倍の悪化が観測されている。もうひとつは、完全版(Full Polonius)が未完で、Alpha安定化後の具体的な計画は当面存在しないことである。安定化は2026年内が目標とされている。

コンパイラ中核の刷新。 次世代のトレイトソルバが2026年8月21日にnightlyで既定になった。トレイトソルバとは、第2回で見た trait について、「この型に対して、どの実装を使えばよいか」を決める部分である。Javaで言えば、どのinterfaceの実装が当てはまるかをコンパイラが解決する処理にあたるが、Rustはこれを型の組み合わせから推論するため、はるかに複雑になる。

公式が「初回リリース以来、コンパイラへの最大の単一変更」と表現しており、200件超の既知の問題を解消する。数か月内の安定化予定だが、具体的な時期は未確定である。

そして、非同期の矛盾

議論で最も鋭かった指摘がここである。

第4回で見たとおり、Rustの非同期には未整備の項目が残っている。&dyn Trait での非同期の動的ディスパッチ、trait実装での Send 境界、async再帰、スコープ付き借用、非同期のリソース解放――Rust自身が2026年ロードマップで未整備として列挙している。非同期の実行基盤は標準ライブラリに入っておらず、ロードマップにも導入項目はない。

ここから先の指摘は、二つの事実を突き合わせたものである。順に置く。

事実1。Rustの保証が最も効くのは、同期のコードである。 第1回で見たとおり、Rustの検査は「値がどこからどこまで生きるか」をコードの構造から追う。ところが非同期の処理は、途中で中断して後で再開する。中断をまたいで借用を持ち越すと、この追跡が難しくなる。 上に挙げた未整備の項目は、そのほとんどがここに由来している。第2回で見た dyn による動的ディスパッチが非同期で使えないのも、非同期の後始末(第3回の Drop)が未整備なのも、同じ根から出ている。

事実2。非同期を最も必要とするのは、Webと業務システムである。 多数の接続を同時に抱え、そのほとんどは応答を待っているだけ、という形の処理だからである。待っているあいだスレッドを占有しないための仕組みが非同期であり、第4回で見たJavaの仮想スレッドは、まったく同じ問題を別の方法で解いていた。

この二つを重ねると、こうなる。

Rustの「コンパイラが安全性を保証する」が最も強く成立するのは、実質的に同期コードの文脈である。

そして非同期を多用するのは、Webや業務システムという、Rustが最も拡大したい領域である。

安全性の保証が最も弱い場所と、拡大したい市場が重なっている。 これはRustが現時点で抱えている構造的な矛盾であり、未解消である。

「Rustは安全だから業務システムにも」という議論をするなら、この一点は外せない。

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

誤解のないように言うと、非同期のRustが動かないという話ではありません。 実際に動かしています。ただ、同期のコードを書いているときの「コンパイラが通ったから大丈夫」という感覚が、非同期では同じ強さでは効かない。第4回のデッドロックと同じで、言語が保証している範囲の外に出ていることを、自分で意識し続ける必要があります。 そこを分かったうえで使うぶんには問題ありません。

認証の世界は、着実に進んでいる

一方で、機能安全の方向は進展している。Safety-Critical Rust Consortiumが2024年6月に発足し、Ferrocene Language SpecificationをRustプロジェクトが2025年3月に採用した。2026年のロードマップには、MC/DCカバレッジへの対応などが並ぶ。これは航空や自動車の安全規格が要求するテスト網羅の指標で、「行を通ったか」よりはるかに厳しく、条件式の中の一つひとつの条件が、単独で結果を変えうることを確かめたかまで問う。この指標に対応できることが、認証を取る前提になる。

ただし第5回で確認したとおり、認証は製品とツールチェーンの単位で行われ、Rust言語自体が認証されるわけではない

カーネル側。 Rust for Linuxの2026年目標は「機能フラグ無しの安定版Rustでカーネルをビルドする」ことである。そのために必要な安定化として、いくつかの言語機能が具体的に列挙されている。裏を返せば、現状はまだそこに達していない

言語の版。 次のEdition(2027年版など)について、公式のアナウンスもRFCも存在しない。「3年周期だから次は2027年版」という書き方には根拠がない。

二つの認知モデル

議論で出たもうひとつの整理を紹介したい。技術ではなく、組織の話である。

Rustは、コンパイラに安全性を証明させるモデル。

Javaは、実行基盤に最適化と管理を委ねるモデル。

抽象的に聞こえるので、連載で実際に見たものに戻して確かめる。

Rust側では、書き始める前に決めなければならないことがあった。第1回の所有権――この値の持ち主は誰か。第4回の SendSync――この型は別のスレッドへ渡してよいか、共有してよいか。決めないとコンパイルが通らないので、先送りできない。 学び始めの壁はここにある。

Java側では、その多くを実行基盤が引き受けていた。第3回のGCは解放の時点を、第4回の仮想スレッドは待ちの扱いを、書き手に代わって決める。だから最初は書き始めやすい。 そのかわり、第3回のGCによる停止や第4回の静かに壊れるHashMapのように、問題が起きてから実行基盤の挙動を知ることになる。

つまり難しさの総量が違うのではなく、難しさを支払う時点が違う。 第1回の分岐が、そのまま人の育て方にも現れている、ということである。

そして、ここからが議論の指摘だった。

組織の認知モデルは、技術ロードマップよりはるかに遅く変わる。

これは技術選定において、ロードマップと同等かそれ以上に重い判断要素である。JDK 28がいつ出るかより、自分の組織がどちらのモデルで人を育ててきたかのほうが、実際の成否を左右する可能性がある。

なお議論では、両者はそもそも別の人材プールで動いており「競合」より「重ならない市場」と見るべきではないか、という視点も出た。ただしこれは未確定として記録しておく。

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

5年後に何が使えるようになるかより、5年かけて自分たちの組織が何を身につけられるかのほうが、現実には効きます。ロードマップは待てば来ますが、人の育成は待っても来ません。

今後5年で、実際に判断を迫られる場面

議論が挙げた具体的な時機を整理する。予測ではなく、公表されている日付である。

(1)今月――Oracle JDK 21のライセンス切り替え

これは5年後の話ではない。2026年9月以降に出るOracle JDK 21の更新から、無償ライセンスの条件が切り替わる。 継続して使うのか、別のディストリビューションへ移るのか、有償サポートの枠を確保するのか。技術論より先に動く調達の話である。

あわせてGraalVMについても触れておく。GraalVMは、Javaのプログラムを起動の速い実行ファイルに変換できる実行環境である(この変換をNative Imageと呼ぶ)。起動時間が問題になる用途で使われる。

そのGraalVMはJDK 25をベースラインとする月次リリースへ移行しており、JDK 26版は存在しない。さらに、PGO――事前に一度動かして得た実行の傾向を最適化に使う仕組み――と、Native ImageでのG1 GCの利用は、無償のCommunity Editionでは使えず、Oracle GraalVM限定である。

つまり性能の上限をどこに置くかが、そのままライセンスの費用と直結する構造になっている。技術の比較表には出てこないが、実際の選定ではここが効く。

(2)2027年3月前後――JDK 28のGA

値クラス、Vector API、Shenandoahの世代別モード既定化。このセットが計画どおり揃うかを確認する時機である。計算が重いワークロードへの投資判断は、ここを見てからでも遅くない。遅れた場合は、依存している機能群ごと見直しになる。

(3)2026年〜2027年――Poloniusと次世代トレイトソルバの安定化

借用検査の表現力の向上が、コンパイル時間の悪化と引き換えにどれだけ実用的になるか。同時に、非同期の安全性保証がどこまで整備されるかを観察する。

(4)中期――自分たちがどの層で価値を生むか

議論の整理はこうだった。

  • 業務ロジックとクラウド運用の層にとどまるなら、Javaのエコシステムは依然として代替が難しい。Leydenの出荷済みの成果も含めて
  • カーネル、ファームウェア、安全認証、組込みの層なら、Rustの浸透は確実に進む

なお議論では、計算の主役がGPUやNPUへ移ることで構図がさらに変わりうる、という話も出た。ただしこれは裏付けのある評価ができず、推測の域を出ないため、そのまま推測として記録する。

連載を通して残る問い

7回を通して、編集部は「どちらが優れているか」を一度も結論にしなかった。それは中立を装ったのではなく、問いの立て方が間違っていると考えたからである。

議論が最後に置いた問いは、二つだった。

プログラムの正しさを、誰が保証すべきか。コンパイラか、実行時の基盤か。

自分たちのシステムは、5年後にどの層で価値を生んでいるか。

言語選択は、この二つへの答えに従属する変数にすぎない。

第1回で見た「安全性を保証するコストを、実行基盤に払わせるか、コードとコンパイル時に払わせるか」という分岐は、7回を通して形を変えながら現れ続けた。設計の語彙では型と規約の置き場所として、メモリでは解放判断の時点として、並行処理では誤りを検出する工程として、実績では新規と既存の境界として、AIでは仕様をどこに残すかとして。

そして最終回で見たロードマップは、両者が互いの方向へ少しずつ動きながら、それぞれの中核は手放していないことを示している。Javaは値型でメモリの効率に近づくが、配置の制御権は最適化エンジンに置いたままにする。Rustは非同期を整備しようとしているが、その領域こそ保証が最も薄い。

境界は動く。だが、消えはしない。

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

  • Full Poloniusの安定化時期は、公式に未定と明言されている。
  • 非同期の未整備項目(動的ディスパッチ、非同期の解放など)の安定版移行時期は未定である。
  • 構造化並行性がJDK 28で正式化されるかは、現時点でターゲットに入っておらず見通しが立たない。
  • プリミティブ特殊化ジェネリクス(JEP 218)は2017年から停滞しており、再び動くかどうか分からない。
  • GPUやNPUへの計算層のシフトが言語選択の構図をどう変えるかは、裏付けのある評価ができない。本稿では推測として明示した。
  • 本稿に挙げた日付は、すべて公表されている予定である。予定は変わりうる。とくにJDK 28のGA予定日は、そこに載る機能群の帰趨を左右する。

梧桐(編集長・進行)

連載の締めくくりとして、答えではなく問いを置きます。7回かけて分かったのは、この二つの言語は同じ問題を別の場所で解いていて、その場所の選び方には、それぞれ筋が通っているということでした。読者のみなさんが自分の案件でどちらを選ぶにせよ、「なぜその場所で解くのか」を説明できる状態になっていれば、この連載の目的は果たせたことになります。長らくお読みいただき、ありがとうございました。

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

キーワード:#Valhalla#Polonius

出典

コメント