Rustのソースファイル構成
Rust では、標準のビルド&パッケージ管理システム Cargo を使いビルドを行います。
このビルドでは、ソースファイルのディレクトリ及びファイル名にかなり独特の制約があります。
単一のソースファイルから単一の実行ファイルを作成する Cargoパッケージ
お馴染みの Hello worldプログラムの構成です。
任意の作業ディレクトリにて、次のcargoコマンドでバイナリクレートを1つ持つパッケージを作成します。
D:\work> cargo new hello_world
- パッケージとは、クレートをビルド・テスト・共有する単位(ひとまとまりのビルド)です。
- クレートとは、実行可能ファイルまたはライブラリファイルを生成する単位です。前者をバイナリクレート、後者をライブラリクレートと呼びます。
cargo new
コマンドでパッケージを作成します。パッケージ名をコマンドの引数で指定します(ここでは hello_world
)。デフォルトではバイナリクレートを1つ持つパッケージが生成されます。
生成されるディレクトリツリーは次となります。
hello_world
+-- src
| +-- main.rs
+-- Cargo.toml
srcディレクトリ直下に生成されるソースファイル main.rs
は、バイナリクレートのクレートルートとなり、このファイルからビルドを開始し、このファイルから辿れるモジュールが芋づる的にビルドされます。
このバイナリクレートの名前は、パッケージ名と同じ(ここではhello_world
)です。
次のcargoコマンドでクレートをビルドします。
D:\work\hello_world> cargo build
Compiling hello_cargo v0.1.0 (D:\work\hello_world)
Finished dev [unoptimized + debuginfo] target(s) in 1.16s
複数のソースファイルから単一の実行ファイルを作成する Cargoパッケージ
モジュール機能を使い、複数のソースファイルから構成されるプログラムをビルドします。
ソースファイルを分割する場合は、モジュールに切り出してモジュールの階層に基づくディレクトリおよびファイル名を持つソースファイルとする必要があります。
クレートルートからモジュール参照
クレートルート(ここではmain.rs
)から、別ファイルに定義されるモジュールを利用します。
クレートルートから直接参照するファイルは、クレートルートが置かれているディレクトリ(src)に、モジュール名に基づくファイル名(ここではgreeting.rs
)で配置します。次のディレクトリツリーに示します。
hello_world
+-- src
| +-- main.rs
| +-- greeting.rs
+-- Cargo.toml
main.rs
に、モジュールgreeting
を参照する記述を入れます。これにより、モジュール名に基づく名前のソースファイル(greeting.rs
)をビルドに含めます。
mod greeting;
fn main() {
println!("{}", greeting::greet());
}
greeting.rs
に記述する要素は、mod greeting {...}
と定義しなくても、モジュールgreeting
に含まれたものとして扱われます。
pub fn greet() -> String {
String::from("Hello, world!")
}
モジュールの階層化
上述のgreeting
モジュールから、さらに別ソースファイルに記述された別モジュール(ここではgreeting::time_class
)を参照します。
クレートルートからのモジュール階層に基づき、greeting::time_class
モジュールを定義するソースファイルはgreeting/time_class.rs
となります。
hello_world
+-- src
| +-- main.rs
| +-- greeting.rs
| +-- greeting
| +-- time_class.rs
+-- Cargo.toml
greeting
モジュールのソースファイル中に time_class
モジュールへの参照を記述します。
mod time_class;
pub fn greet() -> String {
format!("Hello, world!, {}", time_class::classify(12))
}
time_class
モジュールのソースファイルはモジュール名に基づき time_class.rs
とし、パッケージ階層に基づき src/greeting/time_class.rs
に配置します。
pub fn classify(hour: u32) -> &'static str {
match hour {
0 ..=4 => "midnight",
5 ..=8 => "morning",
9 ..=16 => "daytime",
17 ..=19 => "evening",
20 ..=23 => "night",
_ => "out of range"
}
}
さらにモジュール階層を深くする
モジュール greeting::time_class::morning
を定義する場合、morning
モジュールのソースファイルは次の様に配置します。
hello_world
+-- src
| +-- main.rs
| +-- greeting.rs
| +-- greeting
| +-- time_class.rs
| +-- time_class
| +-- morning.rs
+-- Cargo.toml
mod.rs というファイル(Rust 2015の場合)
Rust 2015 では、モジュールディレクトリに mod.rs という特定のファイル名を配置し、これがモジュールのソースファイルとして扱われます。
Rust 2018 でもこの振る舞いは継続して利用可能ですが、互換性のための利用に留めた方がよいと思います。
1つのパッケージに含めるクレートの制約
パッケージの制約として、1つのパッケージに含めることができるクレートは次となります。
- 1個以上のクレートを持つ
- ライブラリクレートは、0個または1個(2個以上は含められない)
- バイナリクレートは、0個以上(複数個を含められる)
プログラムの規模が大きくなると、複数のライブラリから構成したくなります。このときは、ワークスペースを用いて複数のパッケージを束ねます。
まず、ワークスペースのディレクトリを用意し、ワークスペースを定義するCargo.tomlファイルを配置します。
D:\work> mkdir hello_workspace
D:\work> cd hello_workspace
D:\work\hello_workspace>
ワークスペースを作成するcargoコマンドはないようなので、手作業でディレクトリを作成します。
[workspace]
members = [
"hello",
]
Cargo.tomlには、ワークスペースの定義を記述します。ワークスペースを構成するパッケージをmembersに記述します。まずは実行可能ファイルを生成するパッケージ hello を記述しました。後でライブラリを生成するパッケージを追記していきます。
helloパッケージをcargo new
コマンドで生成します。
D:\work\hello_workspace> cargo new hello
Created binary (application) `hello` package
D:\work\hello_workspace>
この時点でのディレクトリ構成は次となります。
D:\work\hello_workspace
+-- Cargo.toml
+-- hello
+-- Cargo.toml
+-- src
+ main.rs
ワークスペース直下のディレクトリで、cargo build
コマンドを実行します。
D:\work\hello_workspace> cargo build
Compiling hello v0.1.0 (C:\Users\toru\study\rustw\learn\hello_workspace\hello)
Finished dev [unoptimized + debuginfo] target(s) in 0.96s
D:\work\hello_workspace>
ビルド成果物は、ワークスペース直下の target ディレクトリに生成されます。
D:\work\hello_workspace
+-- Cargo.lock
+-- Cargo.toml
+-- hello
| +-- Cargo.toml
| +-- src
| + main.rs
+-- target
+-- debug
ワークスペースにライブラリを生成するパッケージを追加
ワークスペースを定義するCargo.tomlにパッケージ名を記載しないで作成すると、次の様に警告メッセージが表示されます。
D:\work\hello_workspace> cargo new morning --lib
warning: compiling this new package may not work due to invalid workspace configuration
current package believes it's in a workspace when it's not:
current: C:\Users\toru\study\rustw\learn\hello_workspace\morning\Cargo.toml
workspace: C:\Users\toru\study\rustw\learn\hello_workspace\Cargo.toml
this may be fixable by adding `morning` to the `workspace.members` array of the manifest located at: C:\Users\toru\study\rustw\learn\hello_workspace\Cargo.toml
Alternatively, to keep it out of the workspace, add the package to the `workspace.exclude` array, or add an empty `[workspace]` table to the package's manifest.
Created library `morning` package
ワークスペースのCargo.tomlにmorningパッケージを追記します。
[workspace]
members = [
"hello",
"morning",
]
この時点のディレクトリ構成は次となります。
D:\work\hello_workspace
+-- Cargo.lock
+-- Cargo.toml
+-- hello
| +-- Cargo.toml
| +-- src
| + main.rs
+-- morning
| +-- Cargo.toml
| +-- src
| + lib.rs
+-- target
ただし、この時点では hello クレートが morning クレートに依存していることが記載されていないのでビルドが通りません。
ワークスペース内のパッケージ間の依存性の記述
helloパッケージのCargo.tomlに依存性を記述します。
:(前略)
[dependencies]
morning = { path = "../morning" }