Cargoワークスペース構成

ワークスペースとは

Cargo ワークスペースは、1つのリポジトリに複数のクレート(Rustのビルド単位)を置き、まとめて扱う仕組みです。ルートの Cargo.toml にメンバーを列挙すると、それらが1つのビルド単位として扱われます。

# ルートの Cargo.toml
[workspace]
members = ["crates/app-core", "src-tauri"]
resolver = "2"

[workspace.dependencies]
serde = { version = "1", features = ["derive"] }

各メンバーは自分の Cargo.toml を持ち、共通の依存はルートから引き継げます。

# crates/app-core/Cargo.toml
[dependencies]
serde = { workspace = true }

何が嬉しいのか

クレートを分けずに1つの巨大なクレートで書いても動きます。それでも分けるのは、分けたことで守られる境界があるからです。

分けない場合ワークスペースで分けた場合
どのコードからどのコードも呼べるクレート境界を越える呼び出しは pub を通す必要があり、公開範囲が明示される
1行変えると全体が再ビルドされる変更のあったクレートとその依存元だけが再ビルドされる
依存バージョンが用途ごとにばらつく[workspace.dependencies] で1か所に集約できる
テストが全部まとめて走るクレート単位でテストを回せる

とくに効くのが再ビルド範囲です。UI層を触るたびにコアロジックまでビルドし直す状態は、開発の待ち時間として毎日効いてきます。

典型的な分け方

デスクトップアプリ(Tauri)を例にすると、次のように分かれます。

repo/
├─ Cargo.toml            # [workspace] members を列挙
├─ crates/
│  └─ app-core/          # ドメインロジック。UIやOSに依存しない
└─ src-tauri/            # Tauri層。ウィンドウ・OS連携・コマンド公開

依存の向きを一方通行にするのが要点です。src-tauri は app-core に依存しますが、逆向きの依存は作りません。こうするとコアロジックは UI 抜きでテストでき、[[自動テスト]] の実行が速く安定します。将来 CLI 版を足すときも、同じコアを別のメンバーから使えます。

依存の向きが逆流すると、この利点はすべて失われます。「コアからUIの状態を読みたい」と思ったときが分岐点で、そこは引数として渡す設計に直すべき箇所です。

実務での注意点

  • Cargo.lock はワークスペース全体で1つ — ルートに置かれ、メンバー間で依存バージョンが揃います。アプリのリポジトリではコミットします
  • バージョン付けの方針を決める — 全メンバーを同じ版で上げるか、クレートごとに独立させるか。公開クレートを含む場合は [[セマンティックバージョニング]] の意味が利用者に伝わる粒度を選びます
  • [[CI/CD]] では --workspace を付ける — cargo test は既定でカレントのメンバーしか見ません。cargo test --workspace / cargo clippy --workspace にしないと、検査されないクレートが静かに生まれます
  • feature の統一に注意 — 同じ依存を複数メンバーが別の feature 指定で使うと、ビルド時に機能が合成されます。片方でしか有効にしていない機能が、もう片方でも有効になっていることがあります

初学者向けポイント

  • 最初から細かく割る必要はありません。1クレートで書き始め、テストしたい塊が見えた時点で切り出すのが失敗しにくい順番です
  • クレート名はディレクトリ名と別に指定できますが、揃えておくとエラーメッセージとの対応が追いやすくなります
  • cargo build -p app-core のように -p で対象メンバーを指定できます。全体をビルドせずに済むので、開発中の確認が速くなります
  • [[静的解析とリンター]] の設定もルートに集約できます。メンバーごとに設定が散らばると、指摘の基準がクレート間でずれます

関連技術とのつながり

  • [[Rust]] — クレートという言語側のビルド単位を、リポジトリの構成へ持ち上げたもの
  • [[自動テスト]] — UIに依存しないコアを切り出すと、テストが速く安定する
  • [[CI/CD]] — --workspace を付けないと検査対象から漏れるクレートが生まれる
  • [[セマンティックバージョニング]] — メンバーごとの版をどう揃えるかの判断に効く
  • [[静的解析とリンター]] — 設定をルートへ集約し、クレート間で基準をそろえる
  • [[tokio(非同期ランタイム)]] — 非同期を使う層と使わない層を別クレートに分けると依存が軽くなる
Q: Cargoワークスペースにクレートを分ける利点として正しいのはどれ?
- [ ] 実行時のメモリ使用量が減る
- [x] 変更のあったクレートとその依存元だけが再ビルドされる
- [ ] 依存パッケージのダウンロードが不要になる
解説: ビルド単位が分かれるため、UI層だけを触ったときにコアロジックまで再ビルドされずに済みます。

Q: コアロジックのクレートとUI層のクレートの依存関係として望ましいのはどれ?
- [x] UI層がコアに依存し、逆向きの依存は作らない
- [ ] 双方向に依存させて自由に呼び合えるようにする
- [ ] コアがUI層に依存する
解説: 一方通行にすることでコアをUI抜きでテストでき、別のフロントエンド(CLIなど)からも再利用できます。

Q: CIで `cargo test` を実行するとき `--workspace` を付けないと何が起きる?
- [ ] ビルドがエラーで停止する
- [x] カレントのメンバーしかテストされず、検査されないクレートが生まれる
- [ ] 依存バージョンがメンバーごとにばらつく
解説: 既定ではカレントのメンバーだけが対象です。エラーにならず静かに漏れるため、CIでは明示的に `--workspace` を指定します。