Progrust Library.

// dictionary

ワークスペース.

ワークスペースは、一緒に管理される1つ以上のパッケージの集まりです1。ルートのCargo.tomlに[workspace]テーブルを置くとその場所がワークスペースルートになり、membersに並べたディレクトリのパッケージがメンバーになります1。メンバーは共通のCargo.lockと出力先のtargetディレクトリを共有します1。

Cargoはメンバー同士が依存し合うとは仮定しないため、メンバーのライブラリクレートを別のメンバーから使うときも依存関係を明示します2。この依存はcrates.ioを介さず、pathでディレクトリを指して宣言します1。

shop/
├── Cargo.lock        ← ワークスペース全体で1つ
├── Cargo.toml        ← [workspace] を書くマニフェスト
├── cart/
│   ├── Cargo.toml
│   └── src/lib.rs
├── register/
│   ├── Cargo.toml
│   └── src/main.rs
└── target/           ← ワークスペース全体で1つ
Cargo.toml
[workspace]
resolver = "3" # 仮想マニフェストでは既定が"1"になるため明示する(補足を参照)
members = ["cart", "register"]
cart/src/lib.rs
/// カートの合計金額を税込(10%)で計算する
pub fn total_price(prices: &[u32]) -> u32 {
    prices.iter().sum::<u32>() * 110 / 100
}
register/Cargo.toml
[package]
name = "register"
version = "0.1.0"
edition = "2024"

[dependencies]
cart = { path = "../cart" } # 同じワークスペースのメンバーをパスで指定する
register/src/main.rs
use cart::total_price; // メンバーのライブラリクレートを名前で参照する

fn main() {
    let prices = [980, 1250, 300]; // カートに入れた商品の価格
    println!("合計金額: {}円", total_price(&prices));
}

ワークスペースルートでcargo run -p registerと実行すると、cartとregisterの両方がビルドされて「合計金額: 2783円」と表示されます(Rust 1.93時点)。-pは対象のメンバーを選ぶオプションです1。なおRust Playgroundは単一パッケージ専用のため、ワークスペースは手元で試してください。

何が共有され、何が共有されないか

対象 扱い
Cargo.lock ルートに1つ。全メンバーが依存の同じバージョンを使う2
targetディレクトリ ルートに1つ。成果物を使い回すため不要な再ビルドが減る2
[profile.*]・[patch]・[replace] ルートのマニフェストでのみ有効。メンバー側に書いても警告が出て無視される1
[dependencies] 共有されない。使うメンバーごとに宣言が要る2

補足

仮想マニフェストとルートパッケージ

上の例のように、ルートのCargo.tomlが[workspace]だけを持ち[package]セクションを持たない形を仮想マニフェスト(virtual manifest)と呼びます1。主役となるパッケージがなく、すべてを別々のディレクトリに並べて整理したい場合に向いています1。逆に[package]のあるマニフェストへ[workspace]を足すと、そのパッケージがワークスペースのルートパッケージになります1。

仮想マニフェストにはpackage.editionがないため、Cargoは依存解決の方式(resolver)をエディションから推測できません1。resolverを明示しないと既定の"1"が使われ、次の警告が出ます(Rust 1.93時点)。

warning: virtual workspace defaulting to `resolver = "1"` despite one or more
workspace members being on edition 2024 which implies `resolver = "3"`
membersに書かなくてもメンバーになるもの

ワークスペースのディレクトリ内にあるpath依存は、書かなくても自動的にメンバーになります1。membersに並べるのは、それ以外に含めたいディレクトリです。membersにはグロブも使えるため、crates/*のようにまとめて指定できます1。含めたくないディレクトリはexcludeで外します1。

なおワークスペース内でcargo newを実行すると、作られたパッケージがmembersへ自動で追記されます(Rust 1.93時点)。

$ cargo new cart --lib
    Creating library `cart` package
      Adding `cart` as member of workspace at `/path/to/shop`
設定をメンバーへ継承させる

[workspace.package]と[workspace.dependencies]に書いた値は、メンバー側でキー.workspace = trueと書くと継承できます(Rust 1.64以降)1。バージョンやライセンス、共通で使う依存の指定を1か所にまとめられます。

Cargo.toml
[workspace.package]
version = "1.2.3"
license = "MIT"

[workspace.dependencies]
regex = "1"
cart/Cargo.toml
[package]
name = "cart"
version.workspace = true
license.workspace = true

[dependencies]
regex.workspace = true

[workspace.dependencies]側ではoptionalを指定できません1(継承するメンバー側でregex = { workspace = true, optional = true }と書くことはできます)。

コマンドの対象を選ぶ

パッケージ単位のコマンドがどのメンバーに効くかは、フラグで決まります1。

実行するコマンド 実行場所 対象
cargo test -p cart どこでも cartメンバーだけ
cargo test --workspace どこでも 全メンバー
cargo test メンバー内 カレントディレクトリのパッケージ
cargo test ルート default-members。未指定なら仮想マニフェストでは全メンバー

default-membersはルートで実行したときの既定の対象を絞るための項目です。ルートパッケージのあるワークスペースで未指定の場合は、ルートパッケージが対象になります1。

Footnotes

  1. Workspaces — The Cargo Book ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 ↩15 ↩16 ↩17

  2. Cargo Workspaces — The Rust Programming Language ↩ ↩2 ↩3 ↩4 ↩5

この辞書が使われているページ

backlinks 1

  1. 辞書パッケージ