Rust 專案管理與 Cargo:工作空間(Workspaces)
簡介
在實際開發中,一個專案往往不只包含單一的 crate,而是由多個相互依賴、共同維護的子模組組成。若每個子模組都獨立管理,會產生大量重複的 Cargo.toml、相同的相依套件版本、以及散落的測試指令,長期下來維護成本會急速上升。
Rust 官方提供的 Cargo 工作空間(Workspace),正是為了解決這類多 crate 專案的管理問題。透過工作空間,我們可以在同一個 Git repository 中統一編譯、測試、發佈,且只需要維護一次共用的相依套件版本。
本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,帶領讀者一步步掌握 工作空間 的使用方式,讓你的 Rust 專案更具可維護性與可擴充性。
核心概念
什麼是工作空間?
工作空間是一個由 根目錄的 Cargo.toml 所定義的集合,裡面可以包含多個 成員 crate(member crates)。根目錄本身不一定是可執行或函式庫 crate,只是一個管理容器。工作空間的特點包括:
- 共用
Cargo.lock:所有成員共用同一個 lock 檔,確保相依套件版本一致。 - 一次編譯多個 crate:執行
cargo build時會同時編譯整個工作空間內所有需要的 crate,減少重複編譯時間。 - 集中測試與文件產生:
cargo test、cargo 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:共享相依套件版本
假設 utils 與 app 都需要 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 test、cargo 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 時失敗。 |
確保 utils 的 version 已升級且先發布,或在根 Cargo.toml 使用 publish = false 暫時關閉發布。 |
| 路徑相依與版本相依混用 | 同時使用 path = "... 與 version = "x.y.z" 會造成混淆。 |
在工作空間內盡量使用路徑相依;若要發佈到 crates.io,改為使用版本相依並移除 path。 |
最佳實踐:
- 統一相依管理:根
Cargo.toml中列出所有共享套件,子 crate 使用{ workspace = true }。 - 保持乾淨的 Git 歷史:只在根目錄保留
Cargo.lock,避免子 crate 產生多餘的 lock 檔。 - 使用
-p或--exclude:在本機開發時可以只針對需要的 crate 執行指令,提升效率。 - 自動化 CI:在 CI pipeline 中加入
cargo fmt --check、cargo clippy --workspace、cargo test --workspace,確保所有成員的程式碼品質。 - 文件與範例分離:將範例程式放在
examples/目錄,並在根Cargo.toml以 glob pattern 加入工作空間成員,保持主程式碼乾淨。
實際應用場景
1. 多平台 CLI 工具
一個支援 Windows、macOS、Linux 的 CLI 常會有 核心函式庫、平台特定的實作、以及 測試套件。使用工作空間,我們可以把核心抽成 core crate,平台特定的程式碼放在 cli-windows、cli-unix 等 binary crate,所有測試共用同一套相依,發布時只需要分別編譯對應平台的二進位檔。
2. 微服務與共享庫
在微服務架構中,各服務可能共享同一套模型(如 protobuf 產生的結構)或工具函式。將這些共用程式碼抽成 common crate,然後在每個服務的 binary crate 中 path 相依,能保證 模型版本一致,且 編譯速度快(因為 Cargo 會快取已編譯的 common)。
3. 教學範例與實驗專案
在教學課程或開源範例庫中,往往會提供多個小範例(example1、example2…),每個範例都依賴相同的基礎函式庫。使用工作空間可以把基礎函式庫放在 lib,各範例作為 binary 成員,讓學習者只需要 cargo run -p example1 即可執行,且不必重複安裝相依套件。
總結
工作空間是 管理多 crate Rust 專案的關鍵工具,它能:
- 統一相依版本,避免版本衝突
- 共用
Cargo.lock,確保建置一致性 - 一次編譯、測試、產生文件,提升開發效率
- 彈性配置成員路徑,適應大型專案的目錄結構
透過本文的概念說明與實作範例,你已經掌握了:
- 如何在根目錄建立工作空間並加入成員
- 成員之間的路徑相依與共享相依套件的寫法
- 常見的陷阱與對應的最佳實踐
- 工作空間在實務專案中的典型應用情境
在日後的 Rust 開發中,把握工作空間的力量,讓你的程式碼庫保持乾淨、可維護、且易於擴充。祝你寫程式愉快,持續在 Rust 的世界裡探索更多可能!