すでに知っていることと比較することで学ぶことができます。最近、トランジティブな依存関係のバージョン解決に関して、Rustがジャバと同じように動作すると思い込んで痛い目に遭いました。この投稿では、この2つを比較したいと思います。すでに知っていることと比較することで学ぶことができます。最近、トランジティブな依存関係のバージョン解決に関して、Rustがジャバと同じように動作すると思い込んで痛い目に遭いました。この投稿では、この2つを比較したいと思います。

Rust と Java における推移的依存関係バージョン解決の比較

2025/09/21 23:00

すでに知っていることと比較することで学びます。最近、私はRustがJavaと同じように推移的依存関係のバージョン解決が機能すると思い込んで痛い目に遭いました。この記事では、両者を比較したいと思います。

依存関係、推移性、およびバージョン解決

各スタックの詳細に入る前に、この領域と関連する問題について説明しましょう。

\ Hello Worldレベルを超えるプロジェクトを開発する際、他の人が以前に直面した問題に遭遇する可能性が高いです。問題が広く存在する場合、誰かが親切で市民意識の高い人が、それを解決するコードをパッケージ化して他の人が再利用できるようにしている可能性が高いです。これで、そのパッケージを使用して、コア問題の解決に集中できます。これは今日の業界がほとんどのプロジェクトを構築する方法であり、他の問題をもたらすとしても:あなたは巨人の肩の上に立っています。

\ 言語にはプロジェクトにそのようなパッケージを追加できるビルドツールが付属しています。ほとんどの場合、プロジェクトに追加するパッケージを依存関係と呼びます。さらに、プロジェクトの依存関係には独自の依存関係がある場合があります:後者は推移的依存関係と呼ばれます。

Transitive dependencies

上の図では、CとDが推移的依存関係です。

\ 推移的依存関係には独自の問題があります。最大の問題は、推移的依存関係が異なるパスから要求されるが、異なるバージョンである場合です。下の図では、AとBの両方がCに依存していますが、異なるバージョンに依存しています。

ビルドツールはプロジェクトにどのバージョンのCを含めるべきでしょうか?JavaとRustでは異なる答えがあります。順番に説明しましょう。

Java推移的依存関係バージョン解決

注意:Javaコードはバイトコードにコンパイルされ、実行時に解釈されます(そして時にはネイティブコードにコンパイルされますが、これは現在の問題領域外です)。まず、実行時依存関係解決とビルド時依存関係解決について説明します。

\ 実行時には、Java仮想マシンはクラスパスの概念を提供します。クラスをロードする必要がある場合、ランタイムは設定されたクラスパスを順番に検索します。次のクラスを想像してください:

public static Main {     public static void main(String[] args) {         Class.forName("ch.frankel.Dep");     } } 

\ コンパイルして実行してみましょう:

java -cp ./foo.jar:./bar.jar Main 

\ 上記は最初にfoo.jar内でch.frankel.Depクラスを探します。見つかった場合、bar.jarにも存在する可能性があるかどうかに関係なく、そこで停止してクラスをロードします。見つからない場合は、bar.jarクラスでさらに検索します。それでも見つからない場合は、ClassNotFoundExceptionで失敗します。

\ Javaのランタイム依存関係解決メカニズムは順序付けられており、クラスごとの粒度を持っています。これは、上記のようにコマンドラインでクラスパスを定義してJavaクラスを実行する場合でも、マニフェストでクラスパスを定義するJARを実行する場合でも適用されます。

\ 上記のコードを次のように変更してみましょう:

public static Main {     public static void main(String[] args) {         var dep = new ch.frankel.Dep();     } } 

\ 新しいコードがDepを直接参照しているため、新しいコードはコンパイル時にクラス解決を必要とします。クラスパス解決は同じ方法で機能します:

javac -cp ./foo.jar:./bar.jar Main 

\ コンパイラはfoo.jar内でDepを探し、見つからない場合はbar.jarで探します。上記はJava学習の旅の始めに学ぶことです。

\ その後、作業単位はクラスではなく、JARとして知られるJava Archiveになります。JARは、バージョンを指定する内部マニフェストを持つ、美化されたZIPアーカイブです。

\ さて、あなたがfoo.jarのユーザーだとします。foo.jarの開発者はコンパイル時に特定のクラスパスを設定し、おそらく他のJARも含めています。自分のコマンドを実行するにはこの情報が必要です。ライブラリ開発者はこの知識をダウンストリームユーザーにどのように伝えるのでしょうか?

\ コミュニティはこの質問に答えるためにいくつかのアイデアを考え出しました:最初に定着した回答はMavenでした。Mavenにはプロジェクトオブジェクトモデルの概念があり、プロジェクトのメタデータと依存関係を設定します。Mavenは、推移的依存関係も自分の依存関係と共にPOMを公開するため、簡単に解決できます。したがって、Mavenは各依存関係の依存関係をリーフ依存関係まで追跡できます。

\ さて、問題文に戻りましょう:Mavenはバージョンの競合をどのように解決するのでしょうか?MavenはCに対してどの依存関係バージョンを解決するでしょうか、1.0または2.0?

\ ドキュメントは明確です:最も近いものです。

Dependency resolution with the same dependency in different versions

上の図では、v1へのパスの距離は2で、Bへ1つ、次にCへ1つです。一方、v2へのパスの距離は3で、Aへ1つ、次にDへ1つ、最後にCへ1つです。したがって、最短パスはv1を指します。

\ しかし、最初の図では、両方のCバージョンがルートアーティファクトから同じ距離にあります。ドキュメントは答えを提供していません。興味があれば、POMでのAとBの宣言順序に依存します!要約すると、Mavenはコンパイルクラスパスに含める重複した依存関係の単一バージョンを返します。

\ AがC v2.0で動作するか、BがC 1.0で動作する場合は素晴らしいです!そうでない場合は、おそらくAのバージョンをアップグレードするか、Bのバージョンをダウングレードして、解決されたCバージョンが両方で動作するようにする必要があります。これは痛みを伴う手動プロセスです—私がどのように知っているか聞いてください。さらに悪いことに、AとBの両方で動作するCバージョンがないことがわかるかもしれません。AまたはBを置き換える時間です。

Rust推移的依存関係バージョン解決

RustはJavaといくつかの点で異なりますが、私たちの議論のために最も関連性が高いのは次のとおりだと思います:

  • Rustはコンパイル時と実行時に同じ依存関係ツリーを持っています
  • すぐに使えるビルドツールCargoを提供しています
  • 依存関係はソースから解決されます

\ それらを一つずつ検討してみましょう。

\ Javaはバイトコードにコンパイルされ、その後それを実行します。コンパイル時と実行時の両方でクラスパスを設定する必要があります。特定のクラスパスでコンパイルし、異なるクラスパスで実行すると、エラーが発生する可能性があります。例えば、依存するクラスでコンパイルするが、そのクラスが実行時に存在しない場合を想像してください。または、存在するが互換性のないバージョンである場合。

\ このモジュラーアプローチとは対照的に、Rustはクレートのコードとすべての依存関係を一意のネイティブパッケージにコンパイルします。さらに、Rustは独自のビルドも提供するため、異なるツールの癖を覚える必要がありません。Mavenについて言及しましたが、他のビルドツールは上記のユースケースでバージョンを解決するための異なるルールを持っている可能性があります。

\ 最後に、Javaはバイナリ(JAR)から依存関係を解決します。対照的に、Rustはソースから依存関係を解決します。ビルド時に、Cargoは依存関係ツリー全体を解決し、必要なすべてのソースをダウンロードし、正しい順序でコンパイルします。

\ これを念頭に置いて、Rustは最初の問題でC依存関係のバージョンをどのように解決するのでしょうか?Javaのバックグラウンドから来た人には奇妙に思えるかもしれませんが、Rustは両方を含みます。実際、上の図では、RustはAをC v1.0でコンパイルし、BをC v2.0でコンパイルします。問題解決です。

結論

JVM言語、特にJavaは、コンパイル時クラスパスと実行時クラスパスの両方を提供します。これによりモジュール性と再利用性が可能になりますが、クラスパス解決に関する問題の扉を開きます。一方、Rustはライブラリであれ実行可能ファイルであれ、クレートを単一の自己完結型バイナリにビルドします。

\ さらに詳しく:

  • Maven - 依存関係メカニズムの紹介
  • Effective Rust - 項目25:依存関係グラフを管理する

2025年9月14日にA Java Geekで最初に公開

免責事項:このサイトに転載されている記事は、公開プラットフォームから引用されており、情報提供のみを目的としています。MEXCの見解を必ずしも反映するものではありません。すべての権利は原著者に帰属します。コンテンツが第三者の権利を侵害していると思われる場合は、削除を依頼するために service@support.mexc.com までご連絡ください。MEXCは、コンテンツの正確性、完全性、適時性について一切保証せず、提供された情報に基づいて行われたいかなる行動についても責任を負いません。本コンテンツは、財務、法律、その他の専門的なアドバイスを構成するものではなく、MEXCによる推奨または支持と見なされるべきではありません。