【Rust】「クレート?パッケージ?モジュール?」をそろそろ理解しませんか?
どのプログラミング言語でも、最初は1つのファイルに適当に処理を書いて実行してみることを繰り返すと思います。しかし、プログラムが少し大きくなると、ファイルやディレクトリを分割したくなります。
Rustでは、コードを整理するために、「クレート」「パッケージ」「モジュール」 という概念が登場します。
この記事では、「クレート」「パッケージ」「モジュール」の違いをシンプルに解説し、適切にプログラムを管理できるようにすることを目的としています。
「クレート」「パッケージ」「モジュール」の整理
クレート
Rustのコンパイル単位。
クレートは2種類あり、バイナリクレートとライブラリクレートに分かれる。
パッケージ
1つ以上のクレートをまとめたもので、Rustのプロジェクト全体を指す。
必ず、Cargo.toml
というファイルを必ず持つ。
モジュール
クレート内のコードを整理する仕組み.(mod で定義)
モジュールを使うことで、クレート内のコードを分割し、管理しやすくなる。
ディレクトリ構成で理解する
~ 最小構成 ~
my_project/ ← これが「パッケージ」
├── Cargo.toml ← パッケージの設定
└── src/
├── main.rs ← バイナリクレート
この構成では、my_project
というディレクトリがRustのパッケージになります。
my_project
パッケージの中には、Cargo.toml
というパッケージの設定ファイルがあり、
src/ディレクトリ内に、main.rs
というバイナリクレートがあります。
パッケージは1つ以上のクレートをまとめたもので、Rustのプロジェクト全体を指す。
が満たされていることがわかります。
~ 少し大きめな構成 ~
my_project/ ← これが「パッケージ」
├── Cargo.toml ← パッケージの設定
└── src/
├── main.rs ← バイナリクレート
├── lib.rs ← ライブラリクレート
├── utils/ ← モジュール(ディレクトリ)
├── calculate_hoge.rs ← モジュール(ファイル)
├── filter_hoge.rs ← モジュール(ファイル)
├── mod.rs ← モジュールのエントリーポイント
この構成では、以下のことがわかります。
- Cargo.toml があるので my_project はRustのパッケージ
- src/main.rs があるので 実行可能なバイナリクレートを持つ
- src/lib.rs があるので ライブラリクレートとしても利用可能
- src/utils/ はディレクトリ単位のモジュールになっており、mod.rs がエントリーポイントになっている(utils 内のコードを use できる)
クレートについて
クレートはRustのコンパイル単位です。
main.rs
または lib.rs
のどちらかがあると、Rustはそのプロジェクトをクレートとして認識します。
-
main.rs
→ バイナリクレート
Rustにおける実行可能ファイルであり、cargo run
を実行すると、main.rs
の処理が実行されます。 -
lib.rs
→ ライブラリクレート
他のコードから使用できるライブラリを定義します。
use
を使用して呼び出します。
例:バイナリクレートのみ
fn main() {
println!("Hello, World!");
}
cargo run
を実行すると、この main.rs の処理が実行されます。
例:ライブラリクレートを使用
pub fn greet() {
println!("Hello, World!!!");
}
use my_project::greet;
fn main() {
greet();
}
ライブラリクレートを作成すると、他のコードから use
を使って呼び出すことが可能になります。
cargo run
を実行すると、main.rs
の処理が実行されますが、main.rs
ではlib.rs
に定義された関数が呼び出されているため、最終的に、lib.rs
の greet関数
が実行されます。
モジュールについて
モジュールはクレート内のコードを整理する仕組みです。
Rustでは、mod
を使ってモジュールを定義します。
モジュールの基本
// utilsというモジュール
mod utils {
pub fn greet() {
println!("Hello, World!!!");
}
}
fn main() {
// utilsというモジュール内にあるgreet関数を呼び出す
utils::greet();
}
-
mod
キーワードを使ってモジュールを作成することができます。 - モジュール内に複数の関数をまとめることで、コードを整理できます。
なぜモジュールを使うのか
例えば、計算機というモジュールを作成し、内部に「足し算」「引き算」「掛け算」「割り算」のような関数を持つことで、コードを整理することができるようになります。
mod calculator {
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
pub fn subtract(a: i32, b: i32) -> i32 {
a - b
}
pub fn multiply(a: i32, b: i32) -> i32 {
a * b
}
pub fn divide(a: i32, b: i32) -> Option<i32> {
if b == 0 {
None
} else {
Some(a / b)
}
}
}
fn main() {
println!("2 + 3 = {}", calculator::add(2, 3));
println!("5 - 2 = {}", calculator::subtract(5, 2));
println!("4 * 3 = {}", calculator::multiply(4, 3));
match calculator::divide(10, 2) {
Some(result) => println!("10 / 2 = {}", result),
None => println!("Cannot divide by zero"),
}
}
ファイル分割したコードの整理
Rustではモジュールを別ディレクトリ、ファイルに分けることができます。
それにより、巨大なプログラムを整理し、管理しやすくすることが可能になります。
構成
my_project/
├── Cargo.toml
└── src/
├── main.rs
├── lib.rs
├── calculator/ ← calculator モジュール(ディレクトリ)
├── mod.rs ← モジュールのエントリーポイント
├── add.rs ← 足し算モジュール
├── subtract.rs ← 引き算モジュール
├── multiply.rs ← 掛け算モジュール
├── divide.rs ← 割り算モジュール
- ディレクトリ単位でモジュールを管理することで、コードが整理されます。
実装
use my_project::calculator::{add, subtract, multiply, divide};
fn main() {
println!("2 + 3 = {}", add::calc(2, 3));
println!("5 - 2 = {}", subtract::calc(5, 2));
println!("4 * 3 = {}", multiply::calc(4, 3));
match divide::calc(10, 2) {
Some(result) => println!("10 / 2 = {}", result),
None => println!("Cannot divide by zero"),
}
}
pub mod calculator;
-
pub mod calculator;
でcalculator/
ディレクトリをRustのモジュールとして登録します。 - 他のファイルからcalculatorモジュールを
use
キーワードを使用して呼び出せるようになります。
// mod.rsで pub mod を宣言することで、各ファイルがモジュールとして扱われる
pub mod add;
pub mod subtract;
pub mod multiply;
pub mod divide;
-
mod.rs
を置くことで、ディレクトリ単位でモジュールを管理できるようになります。 - 各ファイルを
pub mod
で公開することで、calculatorモジュール内で管理されます。
pub fn calc(a: i32, b: i32) -> i32 {
a + b
}
pub fn calc(a: i32, b: i32) -> i32 {
a - b
}
pub fn calc(a: i32, b: i32) -> i32 {
a * b
}
pub fn calc(a: i32, b: i32) -> Option<i32> {
if b == 0 {
None
} else {
Some(a / b)
}
}
crate キーワードによるモジュールパス
「The Rust Programming Language」に記載されている通り、Rust では、crate キーワードを使うことで、現在のクレートのルート(通常は lib.rs や main.rs)からの絶対パス でモジュールやアイテムを参照することができます。
A path can take two forms:
An absolute path is the full path starting from a crate root; for code from an external crate, the absolute path begins with the crate name, and for code from the current crate, it starts with the literal crate.
A relative path starts from the current module and uses self, super, or an identifier in the current module.
ただし、「ファイル分割したコードの整理」の章では、crate
キーワードを使わずに、my_project
というパッケージ名から始める形でモジュールを参照しています。これは、main.rs では crate
キーワードを使用できないためです。
main.rs はバイナリクレートであり、crate
キーワードは「クレートのルート(lib.rs や main.rs)」を指しますが、バイナリクレートはライブラリクレートとは別のルートを持っています。
そのため、main.rs では Cargo.toml
に定義されている name(パッケージ名)を使って、use my_project::xxx
のように参照する必要があります。
一方で、同じクレート内のモジュール同士で参照する場合は crate
キーワードを使用します。
例えば、「ファイル分割したコードの整理」の章で実装したパッケージに、計算結果を出力する report モジュールを実装してみます。
構成
my_project/
├── Cargo.toml
└── src/
├── main.rs
├── lib.rs
├── calculator/ ← calculator モジュール(ディレクトリ)
├── mod.rs ← モジュールのエントリーポイント
├── add.rs ← 足し算モジュール
├── subtract.rs ← 引き算モジュール
├── multiply.rs ← 掛け算モジュール
├── divide.rs ← 割り算モジュール
├── operations/ ← operations モジュール(ディレクトリ)
├── mod.rs ← モジュールのエントリーポイント
├── report.rs ← 計算結果レポートモジュール
- ディレクトリ単位でモジュールを管理することで、コードが整理されます。
実装
use my_project::operations::report;
fn main() {
report::generate(10, 3);
}
pub mod calculator;
pub mod operations;
-
pub mod operations;
を追加します。
// mod.rsで pub mod を宣言することで、各ファイルがモジュールとして扱われる
pub mod add;
pub mod subtract;
pub mod multiply;
pub mod divide;
pub fn calc(a: i32, b: i32) -> i32 {
a + b
}
pub fn calc(a: i32, b: i32) -> i32 {
a - b
}
pub fn calc(a: i32, b: i32) -> i32 {
a * b
}
pub fn calc(a: i32, b: i32) -> Option<i32> {
if b == 0 {
None
} else {
Some(a / b)
}
}
pub mod report;
- 新しくoperationsディレクトリを作成し、ディレクトリ単位でモジュールを管理できるようにします。
use crate::calculator::{add, subtract};
pub fn generate(a: i32, b: i32) {
let sum = add::calc(a, b);
let diff = subtract::calc(a, b);
println!("For a = {} and b = {}:", a, b);
println!(" Sum: {}", sum);
println!(" Difference: {}", diff);
}
- calculator内の各モジュールを参照するのに、
crate
キーワードを使用してい参照パスを指定しています。
上記例のように、main.rsでは use my_project::xxx
で参照するパスを示していましたが、calculator と同一のクレート内にある operations/report.rs
からは crate
キーワードを使用することで、同じライブラリクレート内のルートからの絶対パスで参照できることがわかります。
まとめ
パッケージ = Rustのプロジェクト全体
クレート = コンパイル単位(main.rs
or lib.rs
)
モジュール = コード整理の仕組み(mod
を使って整理)
Discussion