Cargo 專案結構解析
簡介
在 Rust 生態系統中,Cargo 不只是套件管理工具,更是整個開發流程的核心。它負責建置、測試、發佈以及依賴管理,讓開發者可以把注意力放在程式本身的邏輯上。
了解 Cargo 產生的專案目錄與檔案結構,對於新手快速上手、以及中階開發者維護大型程式碼庫都相當重要。正確的結構不僅提升可讀性,也能減少編譯錯誤與依賴衝突的機會。
本篇文章將從 Cargo 初始化 的結果說起,逐層剖析每個目錄與檔案的意義,並提供實作範例、常見陷阱與最佳實踐,最後說明在真實專案中如何運用這些概念。
核心概念
1. Cargo.toml – 專案的「說明書」
Cargo.toml 使用 TOML 語法描述專案的基本資訊、依賴、編譯設定等。最小範例:
[package]
name = "hello_cargo"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
name、version、edition為必填欄位。[dependencies]區塊列出所有外部 Crate,支援版本範圍、功能旗標 (features) 等進階設定。
小技巧:在多套件 (workspace) 中,
Cargo.toml也可以包含[workspace]區塊,統一管理子專案的相依性。
2. src/ 目錄 – 程式碼的主體
| 檔案/目錄 | 說明 |
|---|---|
main.rs |
二進位 (binary) 專案的入口點。執行 cargo run 時會編譯此檔案。 |
lib.rs |
函式庫 (library) 專案的根模組。其他 crate 會以 use crate_name::... 引入。 |
bin/ |
可放置多個二進位檔,每個檔案會產生獨立的可執行檔。 |
tests/ |
整合測試 (integration tests) 的目錄,檔名會被視為獨立測試模組。 |
examples/ |
範例程式碼,cargo run --example <name> 可直接執行。 |
範例 1:簡易 main.rs
// src/main.rs
fn main() {
// 印出 Hello, Cargo!
println!("Hello, Cargo!");
}
範例 2:建立可重複使用的函式庫
// src/lib.rs
/// 計算兩個整數的最大公因數 (GCD)
pub fn gcd(mut a: u64, mut b: u64) -> u64 {
while b != 0 {
let r = a % b;
a = b;
b = r;
}
a
}
// 讓測試模組能存取 `gcd`
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gcd() {
assert_eq!(gcd(48, 18), 6);
}
}
範例 3:多執行檔 (bin/)
my_project/
├─ src/
│ └─ main.rs
└─ src/
└─ bin/
├─ server.rs
└─ client.rs
// src/bin/server.rs
fn main() {
println!("啟動伺服器…");
// 這裡放置網路監聽程式碼
}
// src/bin/client.rs
fn main() {
println!("執行客戶端…");
// 這裡放置連線到伺服器的程式碼
}
執行方式:
cargo run --bin server # 執行 server.rs
cargo run --bin client # 執行 client.rs
3. target/ 目錄 – 編譯產物的暫存區
cargo build、cargo test、cargo run 都會把產出的 .rlib、.exe、.d 等檔案放在 target/。
debug/:預設的除錯編譯,速度較快、包含除錯資訊。release/:使用--release產生最佳化後的二進位檔,適合部署。
注意:
target/不應 被加入版本控制 (git)。在.gitignore中加入target/可以避免不必要的檔案被提交。
4. Cargo.lock – 依賴版本鎖定
當第一次執行 cargo build 時,Cargo 會解析 Cargo.toml 中的相依性,並把解析後的具體版本寫入 Cargo.lock。
- 應該提交:對於應用程式 (binary) 專案,將
Cargo.lock加入版本控制可以保證所有開發者與 CI 環境使用相同的依賴版本。 - 不必提交:對於 library crate,通常不需要提交,讓使用者自行決定相依版本。
5. 工作區 (Workspace)
當一個 repo 包含多個相互關聯的 crate 時,使用 Workspace 可以共用 Cargo.lock、統一編譯設定,減少重複建置。
# Cargo.toml (根目錄)
[workspace]
members = [
"core",
"cli",
"api",
]
每個子目錄 (core/, cli/, api/) 都會有自己的 Cargo.toml,但編譯時會共享同一個 target/。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
忘記 .gitignore |
target/、Cargo.lock(對 library)會被誤加入 Git,導致 repo 膨脹。 |
在專案根目錄放置標準的 .gitignore,內容可參考 https://github.com/github/gitignore/blob/main/Rust.gitignore。 |
| 依賴版本衝突 | 多個 crate 依賴不同版本的同一套件,編譯失敗。 | 使用 cargo tree 檢視依賴樹,必要時在 [patch.crates-io] 或 cargo update -p 針對特定套件強制升級。 |
| 測試檔案放錯位置 | 把單元測試寫在 tests/,但忘了加 #[cfg(test)],導致編譯失敗。 |
單元測試放在 src/*.rs 內的 mod tests,整合測試則放在 tests/。 |
忽略 edition |
使用舊版語法(如 try!)在新 edition 中會警告。 |
在 Cargo.toml 明確指定 edition = "2021",並遵循該版的語法與特性。 |
過度依賴 unwrap() |
在正式二進位檔中直接 unwrap() 會在錯誤時 panic,影響使用者體驗。 |
使用 Result 與 anyhow、thiserror 等錯誤處理庫,提供友善的錯誤訊息。 |
最佳實踐
- 保持目錄乾淨:只在
src/放置程式碼,其他如scripts/、assets/放在根目錄或examples/。 - 使用工作區:大型專案或微服務架構時,將共用程式碼抽成 library,其他 binary 只負責 I/O。
- 定期更新依賴:
cargo outdated可以檢測過期套件,cargo audit會掃描已知安全漏洞。 - CI/CD 整合:在 GitHub Actions、GitLab CI 中加入
cargo fmt -- --check、cargo clippy -- -D warnings,確保程式碼風格與 lint 通過。
實際應用場景
1. 建立 CLI 工具
使用 src/main.rs 作為入口,將核心演算法抽成 src/lib.rs,再在 Cargo.toml 加入 clap、anyhow 等依賴。透過 cargo install --path . 可以直接把工具安裝到使用者的 $HOME/.cargo/bin。
2. 開發 Web 服務
在工作區中建立 api/ (Rocket/Actix) 與 core/(資料模型、商業邏輯)兩個 crate。api 只負責路由與請求處理,core 提供純函式庫,讓未來的 CLI 或測試程式也能重複使用。
3. 撰寫範例與教學
將 examples/ 目錄作為「可執行教學」的容器。每個範例都可以使用 cargo run --example <name> 直接執行,方便新手快速驗證概念。
總結
- Cargo 不只是建置工具,它定義了 Rust 專案的標準結構,讓程式碼、測試、範例與依賴管理都有一致的規範。
- 了解
Cargo.toml、src/、target/、Cargo.lock以及 Workspace 的角色,可大幅提升開發效率與專案可維護性。 - 避免常見的目錄與依賴錯誤,並遵守 最佳實踐(如 CI 整合、錯誤處理、定期更新),即可在實務上建立穩定、可擴充的 Rust 應用。
掌握了 Cargo 的專案結構後,你就能更專注於寫出安全、快速且易於維護的 Rust 程式碼,無論是小型腳本、CLI 工具,或是大型微服務,都能在同一套流程中順利完成。祝開發順利,玩得開心!