Rust における Cargo Workspaces の探求

Beginner

This tutorial is from open-source community. Access the source code

はじめに

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.tomladd_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.tomladd_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_onerandに依存する情報が含まれています。ただし、ワークスペース内のどこかで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.lockadderの依存関係のリストに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 testadd_oneクレートのテストのみを実行し、adderクレートのテストは実行しなかったことを示しています。

ワークスペース内のクレートをhttps://crates.ioに公開する場合、ワークスペース内の各クレートは個別に公開する必要があります。cargo testと同じように、-pフラグを使用して、公開したいクレートの名前を指定することで、ワークスペース内の特定のクレートを公開できます。

追加の練習として、add_oneクレートと同じ方法でこのワークスペースにadd_twoクレートを追加してみてください!

プロジェクトが成長するにつれて、ワークスペースを使用することを検討してください。1 つの大きなコードブロックよりも、理解しやすく、小さな個別のコンポーネントを提供します。さらに、ワークスペース内のクレートを維持することで、同時に頻繁に変更される場合、クレート間のコーディネーションを容易にすることができます。

まとめ

おめでとうございます!あなたは Cargo Workspaces の実験を完了しました。あなたのスキルを向上させるために、LabEx でさらに多くの実験を行ってみてください。