本文 AI 產出,尚未審核

Rust 專案管理與 Cargo:工作空間(Workspaces)

簡介

在實際開發中,一個專案往往不只包含單一的 crate,而是由多個相互依賴、共同維護的子模組組成。若每個子模組都獨立管理,會產生大量重複的 Cargo.toml、相同的相依套件版本、以及散落的測試指令,長期下來維護成本會急速上升。

Rust 官方提供的 Cargo 工作空間(Workspace),正是為了解決這類多 crate 專案的管理問題。透過工作空間,我們可以在同一個 Git repository 中統一編譯、測試、發佈,且只需要維護一次共用的相依套件版本。

本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,帶領讀者一步步掌握 工作空間 的使用方式,讓你的 Rust 專案更具可維護性與可擴充性。


核心概念

什麼是工作空間?

工作空間是一個由 根目錄的 Cargo.toml 所定義的集合,裡面可以包含多個 成員 crate(member crates)。根目錄本身不一定是可執行或函式庫 crate,只是一個管理容器。工作空間的特點包括:

  1. 共用 Cargo.lock:所有成員共用同一個 lock 檔,確保相依套件版本一致。
  2. 一次編譯多個 crate:執行 cargo build 時會同時編譯整個工作空間內所有需要的 crate,減少重複編譯時間。
  3. 集中測試與文件產生cargo testcargo doc 會自動遍歷所有成員,讓測試與文件維護更簡單。

小技巧:若工作空間只想編譯其中一個 crate,可使用 -p <crate-name> 參數指定。

建立第一個工作空間

以下示範如何在本機建立一個簡單的工作空間,包含一個 library crate (utils) 與一個 binary crate (app)。

# 建立根目錄
mkdir my_workspace
cd my_workspace

# 初始化工作空間的根 Cargo.toml(此檔案不需要 [package] 區段)
cat > Cargo.toml <<'EOF'
[workspace]
members = [
    "utils",
    "app",
]
EOF

# 建立 utils 函式庫
cargo new utils --lib

# 建立 app 可執行檔,並指定依賴 utils
cargo new app --bin

此時目錄結構如下:

my_workspace/
├─ Cargo.toml          # 工作空間設定
├─ utils/
│  └─ Cargo.toml      # library crate
└─ app/
   └─ Cargo.toml      # binary crate

成員之間的相依設定

app/Cargo.toml 中,我們把 utils 加入相依:

[package]
name = "app"
version = "0.1.0"
edition = "2021"

[dependencies]
utils = { path = "../utils" }

這樣 app 就可以直接使用 utils 提供的函式:

// app/src/main.rs
use utils::greet;

fn main() {
    greet("Rust 工作空間");
}
// utils/src/lib.rs
/// 傳回一段問候文字
pub fn greet(name: &str) {
    println!("Hello, {}!", name);
}

執行 cargo run -p app,即可看到:

Hello, Rust 工作空間!

範例 1:共享相依套件版本

假設 utilsapp 都需要 serde,我們可以把版本寫在根目錄的 Cargo.toml 中,讓兩個 crate 自動繼承:

# my_workspace/Cargo.toml
[workspace]
members = ["utils", "app"]

[dependencies]
serde = { version = "1.0", features = ["derive"] }

然後在子 crate 的 Cargo.toml 只需要宣告使用:

# utils/Cargo.toml
[package]
name = "utils"
version = "0.1.0"
edition = "2021"

[dependencies]
serde = { workspace = true }   # 直接引用工作空間的設定
# app/Cargo.toml
[package]
name = "app"
version = "0.1.0"
edition = "2021"

[dependencies]
utils = { path = "../utils" }
serde = { workspace = true }

這樣 所有成員會共用同一個 serde 版本,避免因版本不一致導致的編譯衝突。

範例 2:排除不需要的成員

有時候你只想在 CI 中測試特定子 crate,Cargo 允許在根 Cargo.toml 使用 exclude

[workspace]
members = ["utils", "app", "bench"]
exclude = ["bench"]   # CI 時不會編譯 bench

在本機仍然可以手動 cargo run -p bench,但預設的 cargo testcargo build 都不會觸及 bench

範例 3:自訂工作空間成員的路徑

如果子 crate 放在非同層目錄,仍然可以透過相對路徑或 glob pattern 指定:

[workspace]
members = [
    "crates/*",          # 匹配 crates/ 目錄下所有子目錄
    "examples/cli",     # 單一子目錄
]

這樣的設定讓專案結構更具彈性,適合大型專案把 library、binary、範例分開管理。

範例 4:在工作空間中使用 cargo publish

當你要把多個 crate 同時發布到 crates.io 時,只要在根目錄執行:

cargo publish -p utils
cargo publish -p app

若想一次發佈所有成員(且每個 crate 都已設定好 publish = true),可以使用:

cargo publish --workspace

Cargo 會自動依照相依順序發布,確保 utils 先上傳,再讓 app 能正確找到它。

範例 5:工作空間的測試與文件產生

# 同時跑所有成員的測試
cargo test --workspace

# 產生所有成員的 API 文件,放在 target/doc/
cargo doc --workspace --open

這兩個指令讓 CI 流程變得非常簡潔,只需要一條命令就能覆蓋整個專案。


常見陷阱與最佳實踐

陷阱 說明 解決方式
Cargo.lock 被意外提交到子 crate 工作空間應只保留根目錄的 Cargo.lock,子 crate 不需要自己的 lock 檔。 確認 .gitignore 中包含 */Cargo.lock,或手動刪除子目錄的 lock 檔。
相依版本不一致 若子 crate 在 Cargo.toml 中自行指定版本,會導致工作空間內產生多個相同套件的編譯產物。 使用 workspace = true 或在根 Cargo.toml 中統一管理相依版本。
測試執行過慢 cargo test --workspace 會同時編譯所有成員,對大型專案可能耗時。 使用 --exclude 排除不必要的成員,或在 CI 中分段測試(例如只跑 utils 的單元測試)。
發布順序錯誤 app 依賴尚未發布的 utils,會在 cargo publish --workspace 時失敗。 確保 utilsversion 已升級且先發布,或在根 Cargo.toml 使用 publish = false 暫時關閉發布。
路徑相依與版本相依混用 同時使用 path = "...version = "x.y.z" 會造成混淆。 在工作空間內盡量使用路徑相依;若要發佈到 crates.io,改為使用版本相依並移除 path

最佳實踐

  1. 統一相依管理:根 Cargo.toml 中列出所有共享套件,子 crate 使用 { workspace = true }
  2. 保持乾淨的 Git 歷史:只在根目錄保留 Cargo.lock,避免子 crate 產生多餘的 lock 檔。
  3. 使用 -p--exclude:在本機開發時可以只針對需要的 crate 執行指令,提升效率。
  4. 自動化 CI:在 CI pipeline 中加入 cargo fmt --checkcargo clippy --workspacecargo test --workspace,確保所有成員的程式碼品質。
  5. 文件與範例分離:將範例程式放在 examples/ 目錄,並在根 Cargo.toml 以 glob pattern 加入工作空間成員,保持主程式碼乾淨。

實際應用場景

1. 多平台 CLI 工具

一個支援 Windows、macOS、Linux 的 CLI 常會有 核心函式庫平台特定的實作、以及 測試套件。使用工作空間,我們可以把核心抽成 core crate,平台特定的程式碼放在 cli-windowscli-unix 等 binary crate,所有測試共用同一套相依,發布時只需要分別編譯對應平台的二進位檔。

2. 微服務與共享庫

在微服務架構中,各服務可能共享同一套模型(如 protobuf 產生的結構)或工具函式。將這些共用程式碼抽成 common crate,然後在每個服務的 binary crate 中 path 相依,能保證 模型版本一致,且 編譯速度快(因為 Cargo 會快取已編譯的 common)。

3. 教學範例與實驗專案

在教學課程或開源範例庫中,往往會提供多個小範例(example1example2…),每個範例都依賴相同的基礎函式庫。使用工作空間可以把基礎函式庫放在 lib,各範例作為 binary 成員,讓學習者只需要 cargo run -p example1 即可執行,且不必重複安裝相依套件。


總結

工作空間是 管理多 crate Rust 專案的關鍵工具,它能:

  • 統一相依版本,避免版本衝突
  • 共用 Cargo.lock,確保建置一致性
  • 一次編譯、測試、產生文件,提升開發效率
  • 彈性配置成員路徑,適應大型專案的目錄結構

透過本文的概念說明與實作範例,你已經掌握了:

  1. 如何在根目錄建立工作空間並加入成員
  2. 成員之間的路徑相依與共享相依套件的寫法
  3. 常見的陷阱與對應的最佳實踐
  4. 工作空間在實務專案中的典型應用情境

在日後的 Rust 開發中,把握工作空間的力量,讓你的程式碼庫保持乾淨、可維護、且易於擴充。祝你寫程式愉快,持續在 Rust 的世界裡探索更多可能!