Rustでの開発において、パッケージマネージャーであるCargoは欠かせない存在です。
その中でもCargo.tomlとCargo.lockという2つのファイルは、プロジェクトの依存関係を管理する上で非常に重要な役割を担っています。
しかし、初心者から中級者にかけて「なぜ設定ファイルが2つも必要なのか」「Cargo.lockを手動で編集してはいけないのはなぜか」といった疑問を持つ方も少なくありません。
2026年現在のRust開発においても、この2つのファイルの性質を正しく理解することは、チーム開発やCI/CD環境におけるビルドの安定性を保つための基本です。
本記事では、Cargo.lockが果たす具体的な役割と、Cargo.tomlとの明確な使い分け、そして依存関係が更新される仕組みについて、プロフェッショナルな視点から詳しく解説します。
Cargo.tomlとCargo.lockの根本的な違い
Rustプロジェクトのルートディレクトリには、必ずといっていいほどこれら2つのファイルが存在します。
まずは、それぞれのファイルがどのような目的で存在しているのか、その根本的な違いを整理しましょう。
Cargo.tomlは「開発者の意図」を記す場所
Cargo.tomlはマニフェストファイルと呼ばれ、開発者がプロジェクトのメタデータや必要なライブラリの範囲を記述するために使用します。
- プロジェクト名、バージョン、著者情報などの基本設定
- 直接依存するライブラリ(クレート)の名前と許容されるバージョン範囲
- ビルドプロファイル(dev, release)の設定
- フィーチャーフラグ(features)の定義
ここで重要なのは、依存ライブラリのバージョンを指定する際、多くの場合はセマンティックバージョニング(SemVer)に基づいた「範囲」で指定するという点です。
例えば、rand = "0.8"と記述した場合、これは「0.8.0以上、0.9.0未満」の最新バージョンを許可することを意味します。
Cargo.lockは「ビルドの再現性」を保証する場所
一方でCargo.lockは、Cargoが依存関係を解決した結果を記録する「スナップショット」です。
開発者が直接編集することは想定されておらず、Cargoが自動的に生成・更新を行います。
- 実際にダウンロードされた各ライブラリの正確なバージョン(例:0.8.5)
- 依存ライブラリがさらに依存している「間接的な依存関係(過渡的依存関係)」の全リスト
- 各パッケージの整合性を確認するためのチェックサム(ハッシュ値)
このファイルが存在することで、自分のPCでビルドに成功したコードが、他の開発者のPCやサーバー上でも「全く同じバージョンのライブラリ」を用いてビルドされることが保証されます。
| 項目 | Cargo.toml | Cargo.lock |
|---|---|---|
| 役割 | 依存関係の定義とプロジェクト設定 | 依存関係の確定と状態の保存 |
| 編集者 | 開発者が手動で行う | Cargoが自動で行う |
| バージョンの書き方 | セマンティックバージョンによる「範囲」 | 特定の「固定値」 |
| 管理対象 | 直接的な依存関係が主 | 直接・間接を含む全依存関係 |
Cargo.lockが果たす重要な役割
なぜ単なる「範囲指定」だけでなく、詳細な固定値が必要なのでしょうか。
それには、現代のソフトウェア開発における複雑な依存構造が関係しています。
1. 再現性の確保(Deterministic Builds)
もしCargo.lockが存在せず、毎回Cargo.tomlの範囲指定から最新バージョンを探しに行く仕組みだったとしましょう。
ある日、依存しているライブラリが「バグを含んだ新しいマイナーバージョン」をリリースした場合、昨日まで動いていたプロジェクトが、コードを一行も変えていないのに突然コンパイルエラーになったり、実行時にクラッシュしたりする可能性があります。
Cargo.lockがあれば、Cargoはまずそのファイルを参照し、そこに記述されている特定のバージョンを優先的に使用します。
これにより、「いつ、どこでビルドしても同じバイナリが得られる」という再現性が担保されます。
2. 依存ツリー全体の整合性維持
現代のライブラリは、さらに別の多くのライブラリに依存しています。
これを依存ツリー(Dependency Tree)と呼びます。
例えば、ライブラリAとライブラリBの両方がライブラリCに依存している場合、CargoはAとBの要求を満たす最適なCのバージョンを計算しなければなりません。
この計算結果は複雑になることが多く、一度導き出した「最適解」をCargo.lockに保存しておくことで、再計算による意図しない不整合を防ぐことができます。
3. サプライチェーン攻撃への対策
Cargo.lockには各パッケージのハッシュ値が含まれています。
これは、ダウンロードされたソースコードが、前回ビルドした時と同一であることを証明するものです。
万が一、外部のリポジトリに公開されているライブラリが密かに改ざんされたとしても、ハッシュ値が一致しなければCargoはビルドを中断し、セキュリティ上のリスクを未然に防ぐことができます。
依存関係が更新される仕組み
Cargo.lockは自動生成されますが、いつ、どのようなタイミングで内容が書き換わるのかを把握しておくことは重要です。
cargo build 実行時の挙動
新しい依存関係をCargo.tomlに追加してcargo buildを実行すると、Cargoは以下のステップを踏みます。
Cargo.tomlの内容を確認する。Cargo.lockに既に記載されているバージョンが、Cargo.tomlの条件を満たしているかチェックする。- 条件を満たしていない場合(新規追加時など)、適切なバージョンをレジストリから検索する。
- 解決された新しい依存関係の情報を
Cargo.lockに書き出す。
明示的な更新:cargo update
既にCargo.lockにバージョンが固定されている場合、cargo buildを実行しても(Cargo.tomlを書き換えない限り)ライブラリのバージョンは上がりません。
許容範囲内の最新バージョンに更新したい場合は、cargo updateコマンドを使用します。
// 例:特定のパッケージのみを更新する場合
// ターミナルで実行
// cargo update -p rand
Updating crates.io index
Updating rand v0.8.4 -> v0.8.5
このコマンドを実行することで、Cargo.tomlで指定した範囲内での最新版が取得され、Cargo.lockが更新されます。
コードの変更なしにライブラリだけを最新にしたい場合は、このステップを意図的に踏む必要があります。
実践:Git管理におけるCargo.lockの扱い
チーム開発において、Cargo.lockをGitなどのバージョン管理システムに含めるべきかどうかは、プロジェクトの種類によって決まります。
これはRustコミュニティにおいて明確なガイドラインが存在します。
アプリケーション(バイナリ)プロジェクトの場合
実行可能なアプリケーション(main.rsを持つもの)を開発している場合、必ず Cargo.lock をリポジトリにコミットしてください。
開発チームの全員が同じバージョンの依存関係を使用し、本番環境のCI/CDパイプラインでも全く同じ環境を構築するためです。
これにより、「自分の環境では動くが、サーバーでは動かない」という問題を根絶できます。
ライブラリプロジェクトの場合
他のプロジェクトから利用されることを目的としたライブラリを開発している場合、歴史的には「Cargo.lockをコミットしない」ことが推奨されてきました。
理由は、ライブラリの利用者がそれぞれ独自のCargo.lockを持つため、ライブラリ側のロックファイルは無視されるからです。
しかし、2026年現在のベストプラクティスとしては、ライブラリであっても Cargo.lock をコミットする手法が一般的になっています。
これは、ライブラリ自体のテスト(CI)を安定させるためです。
ただし、ライブラリが「最新の依存関係でも正しく動作するか」を確認するために、CI上で定期的にcargo updateを実行するジョブを設けることが推奨されます。
トラブルシューティング:コンフリクトへの対処
複数人で開発していると、Gitのブランチをマージする際にCargo.lockでコンフリクト(衝突)が発生することがあります。
Cargo.lockは巨大なファイルになることが多く、手動でマージしようとするのは非常に危険です。
手動での修正は避け、Cargoの機能を活用しましょう。
- まず
Cargo.tomlのコンフリクトを解決し、正しく保存します。 - ターミナルで
cargo fetchまたはcargo buildを実行します。 - Cargoが自動的に現在の
Cargo.tomlに基づいてCargo.lockを再計算し、コンフリクトを解消した状態に上書きしてくれます。 - 更新された
Cargo.lockをコミットします。
このように、Cargo.lockは常に「正解の状態」を自動生成できるため、無理にエディタで開いて修正する必要はありません。
まとめ
本記事では、RustプロジェクトにおけるCargo.lockの役割と、Cargo.tomlとの違い、そして運用上の注意点について解説しました。
Cargo.tomlは開発者の「意図(範囲指定)」を記述し、Cargo.lockはCargoが解決した「結果(固定値)」を保持します。Cargo.lockがあることで、ビルドの再現性・整合性・セキュリティが保たれます。- アプリケーション開発では必ずGitに含め、ライブラリ開発でもCIの安定のために含めるのが現代のスタンダードです。
- 更新は
cargo updateで行い、コンフリクト時もCargoに自動解決を任せるのが鉄則です。
これらの仕組みを正しく理解し活用することで、Rustの強力なエコシステムを最大限に引き出し、安全でメンテナンス性の高いコードベースを維持することができるようになります。
日々の開発において、Cargo.lockを単なる自動生成ファイルとして放置するのではなく、プロジェクトの安定性を支える重要な守護者として意識してみてください。
