はじめに
Cargo Workspacesへようこそ。この実験は、Rust Bookの一部です。LabEx で Rust のスキルを練習することができます。
この実験では、Cargo のワークスペース機能を調べます。この機能は、パッケージを複数のライブラリクレートに分割することで、並行して開発される複数の関連パッケージを管理するのに役立ちます。
Cargo Workspaces
12 章では、バイナリクレートとライブラリクレートを含むパッケージを作成しました。プロジェクトが進むにつれて、ライブラリクレートがますます大きくなり、パッケージをさらに複数のライブラリクレートに分割したいと思うかもしれません。Cargo には、並行して開発される複数の関連パッケージを管理するのに役立つ「ワークスペース」と呼ばれる機能があります。
ワークスペースの作成
「ワークスペース」は、同じ「Cargo.lock」と出力ディレクトリを共有する一連のパッケージです。ワークスペースを使ってプロジェクトを作成してみましょう。単純なコードを使うので、ワークスペースの構造に集中できます。ワークスペースを構造化する方法は複数ありますが、一般的な方法の 1 つを示します。バイナリと 2 つのライブラリを含むワークスペースを作成します。メインの機能を提供するバイナリは、2 つのライブラリに依存します。1 つのライブラリはadd_one関数を、もう 1 つのライブラリはadd_two関数を提供します。これらの 3 つのクレートは同じワークスペースの一部になります。まず、ワークスペース用の新しいディレクトリを作成します。
mkdir add
cd add
次に、addディレクトリ内で、ワークスペース全体を構成するCargo.tomlファイルを作成します。このファイルには[package]セクションはありません。代わりに、[workspace]セクションから始まり、バイナリクレートがあるパッケージのパスを指定することで、ワークスペースにメンバーを追加できます。この場合、そのパスは「adder」です。
ファイル名:Cargo.toml
[workspace]
members = [
"adder",
]
次に、addディレクトリ内でcargo newを実行してadderバイナリクレートを作成します。
$ cargo new adder
Created binary (application) `adder` package
この時点で、cargo buildを実行することでワークスペースをビルドできます。addディレクトリ内のファイルは次のようになっているはずです。
├── Cargo.lock
├── Cargo.toml
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
ワークスペースには、コンパイル済みのアーティファクトが配置されるトップレベルの 1 つのtargetディレクトリがあります。adderパッケージには独自のtargetディレクトリはありません。たとえadderディレクトリ内からcargo buildを実行しても、コンパイル済みのアーティファクトはadd/targetに配置され、add/adder/targetにはなりません。Cargo はワークスペース内のtargetディレクトリをこのように構造化します。なぜなら、ワークスペース内のクレートは互いに依存するように意図されているからです。各クレートが独自のtargetディレクトリを持つ場合、各クレートはワークスペース内の他のクレートすべてを再コンパイルして、アーティファクトを独自のtargetディレクトリに配置しなければなりません。1 つのtargetディレクトリを共有することで、クレートは不要な再ビルドを回避できます。
ワークスペース内の 2 番目のパッケージの作成
次に、ワークスペース内に別のメンバーパッケージを作成して「add_one」と呼びましょう。トップレベルのCargo.tomlを変更して、membersリストに「add_one」のパスを指定します。
ファイル名:Cargo.toml
[workspace]
members = [
"adder",
"add_one",
]
次に、新しいライブラリクレート「add_one」を生成します。
$ cargo new add_one --lib
Created library $(add_one) package
この時点で、あなたのaddディレクトリには以下のディレクトリとファイルがあるはずです。
├── Cargo.lock
├── Cargo.toml
├── add_one
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
add_one/src/lib.rsファイルに、add_one関数を追加しましょう。
ファイル名:add_one/src/lib.rs
pub fn add_one(x: i32) -> i32 {
x + 1
}
これで、バイナリを持つadderパッケージが、ライブラリを持つadd_oneパッケージに依存するようになりました。まず、adder/Cargo.tomlにadd_oneへのパス依存を追加する必要があります。
ファイル名:adder/Cargo.toml
[dependencies]
add_one = { path = "../add_one" }
Cargo は、ワークスペース内のクレートが互いに依存することを想定していません。そのため、依存関係を明示する必要があります。
次に、adderクレートでadd_one関数(add_oneクレートから)を使用しましょう。adder/src/main.rsファイルを開き、新しいadd_oneライブラリクレートをスコープ内に持ち込むために、先頭にuse行を追加します。その後、main関数を変更してadd_one関数を呼び出します。以下はリスト 14-7 のようになります。
ファイル名:adder/src/main.rs
use add_one;
fn main() {
let num = 10;
println!(
"Hello, world! {num} plus one is {}!",
add_one::add_one(num)
);
}
リスト 14-7: adderクレートからadd_oneライブラリクレートを使用する
トップレベルのaddディレクトリでcargo buildを実行して、ワークスペースをビルドしましょう!
$ cargo build
Compiling add_one v0.1.0 (file:///projects/add/add_one)
Compiling adder v0.1.0 (file:///projects/add/adder)
Finished dev [unoptimized + debuginfo] target(s) in 0.68s
addディレクトリからバイナリクレートを実行するには、-p引数とパッケージ名を使って、ワークスペース内で実行したいパッケージを指定できます。cargo runを使います。
$ cargo run -p adder
Finished dev [unoptimized + debuginfo] target(s) in 0.0s
Running `target/debug/adder`
Hello, world! 10 plus one is 11!
これは、add_oneクレートに依存するadder/src/main.rsのコードを実行します。
ワークスペース内で外部パッケージに依存する
ワークスペースは、各クレートのディレクトリにCargo.lockがあるのではなく、トップレベルに 1 つだけのCargo.lockファイルを持っていることに注意してください。これにより、すべてのクレートがすべての依存関係の同じバージョンを使用していることが保証されます。もしrandパッケージをadder/Cargo.tomlとadd_one/Cargo.tomlファイルに追加すると、Cargo はそれらの両方をrandの 1 つのバージョンに解決し、それを 1 つのCargo.lockに記録します。ワークスペース内のすべてのクレートが同じ依存関係を使用することは、クレートが常に互換性があることを意味します。add_one/Cargo.tomlファイルの[dependencies]セクションにrandクレートを追加して、add_oneクレートでrandクレートを使用できるようにしましょう。
ファイル名:add_one/Cargo.toml
[dependencies]
rand = "0.8.5"
これで、add_one/src/lib.rsファイルにuse rand;を追加できます。そして、addディレクトリでcargo buildを実行してワークスペース全体をビルドすると、randクレートが取り込まれてコンパイルされます。スコープ内に持ち込んだrandを参照していないため、1 つの警告が表示されます。
$ cargo build
Updating crates.io index
Downloaded rand v0.8.5
--snip--
Compiling rand v0.8.5
Compiling add_one v0.1.0 (file:///projects/add/add_one)
Compiling adder v0.1.0 (file:///projects/add/adder)
Finished dev [unoptimized + debuginfo] target(s) in 10.18s
トップレベルのCargo.lockには、現在、add_oneがrandに依存する情報が含まれています。ただし、ワークスペース内のどこかでrandが使用されている場合でも、他のクレートで使用できるようにするには、それらのCargo.tomlファイルにもrandを追加する必要があります。たとえば、adderパッケージのadder/src/main.rsファイルにuse rand;を追加すると、エラーが表示されます。
$ cargo build
--snip--
Compiling adder v0.1.0 (file:///projects/add/adder)
error[E0432]: unresolved import `rand`
--> adder/src/main.rs:2:5
|
2 | use rand;
| ^^^^ no external crate `rand`
これを修正するには、adderパッケージのCargo.tomlファイルを編集して、randもその依存関係であることを示します。adderパッケージをビルドすると、Cargo.lockのadderの依存関係のリストにrandが追加されますが、randの追加コピーはダウンロードされません。Cargo は、ワークスペース内の各パッケージのすべてのクレートがrandパッケージを使用している場合、同じバージョンを使用していることを保証しており、スペースを節約し、ワークスペース内のクレートが互換性があることを保証します。
ワークスペースにテストを追加する
さらなる機能強化として、add_oneクレート内のadd_one::add_one関数のテストを追加しましょう。
ファイル名:add_one/src/lib.rs
pub fn add_one(x: i32) -> i32 {
x + 1
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
assert_eq!(3, add_one(2));
}
}
次に、トップレベルのaddディレクトリでcargo testを実行します。このように構造化されたワークスペースでcargo testを実行すると、ワークスペース内のすべてのクレートのテストが実行されます。
[object Object]
出力の最初のセクションは、add_oneクレート内のit_worksテストが合格したことを示しています。次のセクションは、adderクレートで 0 個のテストが見つかったことを示しており、最後のセクションは、add_oneクレートで 0 個のドキュメントテストが見つかったことを示しています。
ワークスペースのトップレベルディレクトリから、特定のクレートのテストを実行することもできます。-pフラグを使用して、テストしたいクレートの名前を指定します。
[object Object]
この出力は、cargo testがadd_oneクレートのテストのみを実行し、adderクレートのテストは実行しなかったことを示しています。
ワークスペース内のクレートをhttps://crates.ioに公開する場合、ワークスペース内の各クレートは個別に公開する必要があります。cargo testと同じように、-pフラグを使用して、公開したいクレートの名前を指定することで、ワークスペース内の特定のクレートを公開できます。
追加の練習として、add_oneクレートと同じ方法でこのワークスペースにadd_twoクレートを追加してみてください!
プロジェクトが成長するにつれて、ワークスペースを使用することを検討してください。1 つの大きなコードブロックよりも、理解しやすく、小さな個別のコンポーネントを提供します。さらに、ワークスペース内のクレートを維持することで、同時に頻繁に変更される場合、クレート間のコーディネーションを容易にすることができます。
まとめ
おめでとうございます!あなたは Cargo Workspaces の実験を完了しました。あなたのスキルを向上させるために、LabEx でさらに多くの実験を行ってみてください。