Rustのプログラミング習得において、多くの学習者が最初に直面する壁の一つが「モジュールシステム」の理解です。
Rustには、コードを組織化するための仕組みとして「パッケージ」「クレート」「モジュール」という3つの概念が存在します。
2026年現在、Rustはシステムプログラミングのみならず、Webバックエンドやクラウドネイティブな開発においても主流の言語となりました。
プロジェクトの規模が大きくなるにつれて、これらの概念を正確に使い分ける能力は、保守性の高いコードを書くために不可欠なスキルとなります。
本記事では、特に混同されやすい「パッケージ」と「クレート」の違いに焦点を当て、それらがどのように連携してRustのプロジェクトを構成しているのかを整理して解説します。
クレート(Crate)とは:Rustのコンパイル単位
Rustにおいて、クレートはコンパイルの最小単位を指します。
コンパイラである rustc が一度に処理するコードの塊がクレートであると考えて間違いありません。
クレートには、実行可能なバイナリを生成する「バイナリクレート」と、他のプログラムから利用される「ライブラリクレート」の2種類が存在します。
バイナリクレート
バイナリクレートは、コンパイルすると実行ファイル(Windowsなら .exe ファイルなど)が生成されるクレートです。
このクレートは、必ずプログラムの開始地点となる main 関数を含んでいる必要があります。
一般的に、プロジェクトの src/main.rs がバイナリクレートのルート(基点)となります。
ライブラリクレート
ライブラリクレートは、他のプログラムから呼び出して利用するための機能を定義したクレートです。
これ自体を単体で実行することはできませんが、複数のプロジェクトでコードを再利用するために非常に重要です。
ライブラリクレートのルートは、通常 src/lib.rs となります。
ほとんどのサードパーティ製ライブラリ(Crates.ioで公開されているもの)は、このライブラリクレートの形式で提供されています。
パッケージ(Package)とは:プロジェクトの管理単位
パッケージは、1つ以上のクレートをまとめ、特定の機能セットを提供する単位です。
Rustのビルドツールおよびパッケージマネージャである Cargo を使ってプロジェクトを作成すると、それは一つのパッケージとなります。
パッケージには必ず Cargo.toml というファイルが含まれており、その中にパッケージの名前、バージョン、依存関係などが記述されます。
パッケージが含むことができる構成要素
一つのパッケージが保持できる内容には、明確なルールが存在します。
- ライブラリクレートは最大で1つだけ含めることができます。
- バイナリクレートは任意の数だけ含めることができます。
- 少なくとも1つのクレート(ライブラリまたはバイナリ)を含まなければなりません。
このように、パッケージは「複数のクレートをひとまとめにして管理するためのコンテナ」のような役割を果たしています。
パッケージのディレクトリ構造の例
標準的なRustプロジェクトの構成を見てみましょう。
my-project/
├── Cargo.toml # パッケージの定義ファイル
├── src/
│ ├── main.rs # バイナリクレートのルート
│ └── lib.rs # ライブラリクレートのルート
└── tests/ # 統合テスト
この例では、my-project という一つのパッケージの中に、一つのバイナリクレートと一つのライブラリクレートが共存しています。
パッケージとクレートの違いを比較
ここで、パッケージとクレートの違いを表にまとめて整理します。
| 項目 | クレート (Crate) | パッケージ (Package) |
|---|---|---|
| 定義 | コンパイルの最小単位 | プロジェクトの管理単位 |
| 中心となるファイル | src/main.rs または src/lib.rs | Cargo.toml |
| 役割 | コードの実行、またはAPIの提供 | 依存関係の管理、ビルド設定の保持 |
| 包含関係 | モジュールを包含する | 1つ以上のクレートを包含する |
このように、「クレートはソースコードの集合体」であり、「パッケージはプロジェクト全体を管理する枠組み」であるという違いがあります。
モジュールシステムとの関係性
パッケージとクレートを理解した上で、その中身を整理するのが「モジュール(Module)」です。
クレート内部のコードを論理的に分割し、スコープやカプセル化を管理するために使用されます。
モジュールの階層構造
Rustでは mod キーワードを使用してモジュールを宣言します。
これにより、一つのクレート内に「モジュールツリー」と呼ばれる階層構造が作られます。
// src/lib.rs (ライブラリクレートのルート)
// front_of_houseモジュールの定義
pub mod front_of_house {
// hostingモジュールの定義
pub mod hosting {
pub fn add_to_waitlist() {
println!("Waitlist added.");
}
}
}
// クレート内からの呼び出し
pub fn eat_at_restaurant() {
// 絶対パスによる指定
crate::front_of_house::hosting::add_to_waitlist();
}
上記の例では、クレートを頂点として、その下に front_of_house モジュールがあり、さらにその下に hosting モジュールがあるという階層になっています。
公開設定(pub)の重要性
Rustの要素(関数、構造体、モジュールなど)は、デフォルトではすべてプライベート(非公開)です。
他のモジュールやクレートからアクセス可能にするためには、pub キーワードを付与する必要があります。
これは、パッケージ内でライブラリクレートを作成し、それをバイナリクレートから呼び出す際に非常に重要となります。
Rust 2026年におけるベストプラクティス
現代のRust開発では、プロジェクトの肥大化を防ぐためにパッケージをより細かく分割する傾向があります。
その際に活用されるのが「ワークスペース(Workspace)」という機能です。
ワークスペースによる複数パッケージの管理
ワークスペースを使用すると、複数のパッケージを一つのプロジェクトフォルダで管理し、ビルドキャッシュを共有することができます。
大規模なマイクロサービス開発や、複雑なツール開発においては、機能ごとにパッケージを分けるのが一般的です。
my-workspace/
├── Cargo.toml # ワークスペース定義
├── app-ui/ # パッケージ1 (バイナリ)
│ ├── Cargo.toml
│ └── src/main.rs
└── core-logic/ # パッケージ2 (ライブラリ)
├── Cargo.toml
└── src/lib.rs
このように構造化することで、core-logic というライブラリパッケージを、複数のバイナリパッケージから効率的に利用できるようになります。
具体的なコード例:パッケージ内でのクレート連携
実際に、一つのパッケージ内でライブラリクレートとバイナリクレートを連携させる例を見てみましょう。
まず、src/lib.rs に共通ロジックを記述します。
// src/lib.rs
// クレート外からもアクセスできるようにpubを付与
pub struct Config {
pub query: String,
pub file_path: String,
}
impl Config {
// 構造体のインスタンスを生成する関連関数
pub fn build(args: &[String]) -> Result {
if args.len() < 3 {
return Err("not enough arguments");
}
let query = args[1].clone();
let file_path = args[2].clone();
Ok(Config { query, file_path })
}
}
次に、src/main.rs からこのライブラリを利用します。
同一パッケージ内のライブラリを呼び出す際は、パッケージ名(Cargo.tomlに記述した名前)をクレート名として使用します。
// src/main.rs
use std::env;
use std::process;
// パッケージ名を指定してライブラリクレートをインポート
// ここではパッケージ名が "minigrep" であると仮定します
use minigrep::Config;
fn main() {
let args: Vec = env::args().collect();
// ライブラリクレートの関数を呼び出し
let config = Config::build(&args).unwrap_or_else(|err| {
println!("Problem parsing arguments: {err}");
process::exit(1);
});
println!("Searching for {}", config.query);
println!("In file {}", config.file_path);
}
この構成により、「ロジックはライブラリクレート(lib.rs)に集約し、エントリーポイントはバイナリクレート(main.rs)に置く」という綺麗な分離が可能になります。
$ cargo run -- search_word sample.txt
Searching for search_word
In file sample.txt
まとめ
Rustのプロジェクト構成を理解する鍵は、パッケージ、クレート、モジュールの階層関係を整理することにあります。
最後に、本記事の内容を振り返りましょう。
- クレートは、コンパイルの基本単位であり、バイナリ(実行用)とライブラリ(共通用)の2種類がある。
- パッケージは、
Cargo.tomlで管理されるプロジェクトの単位であり、複数のクレートを含むことができる。 - モジュールは、クレート内部のコードを階層化し、公開範囲を制御するための仕組みである。
- 大規模なプロジェクトでは、ワークスペースを利用して複数のパッケージを効率的に管理する。
これらの概念を正しく使い分けることで、Rustの強力な型システムと安全性、そして高い保守性を最大限に引き出すことができます。
2026年以降のRust開発においても、この基本構造は変わることなく、より効率的な開発を支える基盤となるでしょう。
まずは小さなプロジェクトから、lib.rs と main.rs の使い分けを意識して始めてみてください。
