工作空间(Workspaces)
工作空间是 Rust 中管理多个相关包的强大工具。它允许你在一个项目中组织多个 crate,共享依赖和配置,简化大型项目的管理。
什么是工作空间?
工作空间是一个包含多个包的目录,这些包共享:
- Cargo.lock 文件
- 输出目录 (
target/) - 依赖版本 解析
创建工作空间
基本工作空间结构
my_workspace/
├── Cargo.toml # 工作空间根配置
├── Cargo.lock # 共享的锁文件
├── target/ # 共享的构建目录
├── common/ # 共享库
│ ├── Cargo.toml
│ └── src/
│ └── lib.rs
├── server/ # 服务器应用
│ ├── Cargo.toml
│ └── src/
│ └── main.rs
└── client/ # 客户端应用
├── Cargo.toml
└── src/
└── main.rs
工作空间根配置
# 根目录 Cargo.toml
[workspace]
members = [
"common",
"server",
"client",
]
# 可选:排除某些目录
exclude = [
"old_code",
"experiments",
]
# 工作空间级别的元数据
[workspace.package]
version = "0.1.0"
edition = "2021"
authors = ["Your Name <you@example.com>"]
license = "MIT OR Apache-2.0"
# 共享依赖版本
[workspace.dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.0", features = ["full"] }
clap = "4.0"
anyhow = "1.0"
成员包配置
共享库包
# common/Cargo.toml
[package]
name = "common"
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
[dependencies]
serde.workspace = true
anyhow.workspace = true
# 库特定的依赖
uuid = { version = "1.0", features = ["v4"] }
// common/src/lib.rs
use serde::{Serialize, Deserialize};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
pub id: Uuid,
pub name: String,
pub email: String,
}
impl User {
pub fn new(name: String, email: String) -> Self {
User {
id: Uuid::new_v4(),
name,
email,
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Message {
pub id: Uuid,
pub content: String,
pub user_id: Uuid,
}
pub mod utils {
pub fn validate_email(email: &str) -> bool {
email.contains('@')
}
}
服务器包
# server/Cargo.toml
[package]
name = "server"
version.workspace = true
edition.workspace = true
[dependencies]
common = { path = "../common" }
tokio.workspace = true
serde.workspace = true
anyhow.workspace = true
# 服务器特定依赖
axum = "0.7"
tower = "0.4"
// server/src/main.rs
use common::{User, Message, utils};
use axum::{
extract::Path,
http::StatusCode,
response::Json,
routing::{get, post},
Router,
};
use std::collections::HashMap;
use tokio::sync::Mutex;
use std::sync::Arc;
type UserStore = Arc<Mutex<HashMap<String, User>>>;
#[tokio::main]
async fn main() {
let store: UserStore = Arc::new(Mutex::new(HashMap::new()));
let app = Router::new()
.route("/users", post(create_user))
.route("/users/:id", get(get_user))
.with_state(store);
println!("Server running on http://localhost:3000");
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
.await
.unwrap();
axum::serve(listener, app).await.unwrap();
}
async fn create_user(
axum::extract::State(store): axum::extract::State<UserStore>,
Json(payload): Json<serde_json::Value>,
) -> Result<Json<User>, StatusCode> {
let name = payload["name"].as_str().ok_or(StatusCode::BAD_REQUEST)?;
let email = payload["email"].as_str().ok_or(StatusCode::BAD_REQUEST)?;
if !utils::validate_email(email) {
return Err(StatusCode::BAD_REQUEST);
}
let user = User::new(name.to_string(), email.to_string());
let mut store = store.lock().await;
store.insert(user.id.to_string(), user.clone());
Ok(Json(user))
}
async fn get_user(
Path(id): Path<String>,
axum::extract::State(store): axum::extract::State<UserStore>,
) -> Result<Json<User>, StatusCode> {
let store = store.lock().await;
let user = store.get(&id).ok_or(StatusCode::NOT_FOUND)?;
Ok(Json(user.clone()))
}
客户端包
# client/Cargo.toml
[package]
name = "client"
version.workspace = true
edition.workspace = true
[dependencies]
common = { path = "../common" }
tokio.workspace = true
serde.workspace = true
anyhow.workspace = true
clap.workspace = true
# 客户端特定依赖
reqwest = { version = "0.11", features = ["json"] }
// client/src/main.rs
use common::{User, utils};
use clap::{App, Arg, SubCommand};
use reqwest::Client;
use anyhow::Result;
#[tokio::main]
async fn main() -> Result<()> {
let matches = App::new("User Client")
.version("1.0")
.subcommand(
SubCommand::with_name("create")
.arg(Arg::with_name("name").required(true))
.arg(Arg::with_name("email").required(true))
)
.subcommand(
SubCommand::with_name("get")
.arg(Arg::with_name("id").required(true))
)
.get_matches();
let client = Client::new();
match matches.subcommand() {
("create", Some(sub_m)) => {
let name = sub_m.value_of("name").unwrap();
let email = sub_m.value_of("email").unwrap();
if !utils::validate_email(email) {
eprintln!("Invalid email format");
return Ok(());
}
let user = create_user(&client, name, email).await?;
println!("Created user: {:?}", user);
}
("get", Some(sub_m)) => {
let id = sub_m.value_of("id").unwrap();
let user = get_user(&client, id).await?;
println!("User: {:?}", user);
}
_ => {
println!("Use --help for usage information");
}
}
Ok(())
}
async fn create_user(client: &Client, name: &str, email: &str) -> Result<User> {
let payload = serde_json::json!({
"name": name,
"email": email
});
let response = client
.post("http://localhost:3000/users")
.json(&payload)
.send()
.await?;
let user: User = response.json().await?;
Ok(user)
}
async fn get_user(client: &Client, id: &str) -> Result<User> {
let response = client
.get(&format!("http://localhost:3000/users/{}", id))
.send()
.await?;
let user: User = response.json().await?;
Ok(user)
}
工作空间命令
构建和运行
# 构建整个工作空间
cargo build
# 构建特定包
cargo build -p server
cargo build -p client
# 运行特定包
cargo run -p server
cargo run -p client -- create "Alice" "alice@example.com"
# 测试整个工作空间
cargo test
# 测试特定包
cargo test -p common
发布和管理
# 检查所有包
cargo check --workspace
# 更新依赖
cargo update
# 清理构建产物
cargo clean
# 发布包(需要按依赖顺序)
cargo publish -p common
cargo publish -p server
cargo publish -p client
高级工作空间配置
继承配置
# 根 Cargo.toml
[workspace]
members = ["crate1", "crate2"]
[workspace.package]
version = "1.0.0"
edition = "2021"
rust-version = "1.70"
authors = ["Team <team@example.com>"]
license = "MIT"
repository = "https://github.com/org/project"
[workspace.dependencies]
# 内部依赖
shared-utils = { path = "shared-utils" }
# 外部依赖
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.0", default-features = false }
# crate1/Cargo.toml
[package]
name = "crate1"
# 继承工作空间配置
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
[dependencies]
shared-utils.workspace = true
serde.workspace = true
tokio = { workspace = true, features = ["rt", "net"] }
条件成员
[workspace]
members = [
"core",
"cli",
]
# 根据特性条件包含
[workspace.dependencies]
gui = { path = "gui", optional = true }
# 在特定条件下包含成员
[workspace]
members = [
"core",
"cli",
]
# 使用 resolver 版本
resolver = "2"
最佳实践
1. 项目组织
project/
├── Cargo.toml # 工作空间根
├── README.md
├── LICENSE
├── .gitignore
├── core/ # 核心库
│ ├── Cargo.toml
│ └── src/
├── api/ # API 服务
│ ├── Cargo.toml
│ └── src/
├── cli/ # 命令行工具
│ ├── Cargo.toml
│ └── src/
├── web/ # Web 前端
│ ├── Cargo.toml
│ └── src/
└── tests/ # 集成测试
├── Cargo.toml
└── tests/
2. 依赖管理
# 在工作空间级别统一管理版本
[workspace.dependencies]
# 核心依赖
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.0", features = ["full"] }
anyhow = "1.0"
# 开发依赖
criterion = "0.5"
proptest = "1.0"
# 可选依赖
gui-framework = { version = "0.1", optional = true }
3. 特性管理
# 根 Cargo.toml
[workspace]
members = ["core", "cli", "gui"]
[workspace.dependencies]
core = { path = "core" }
# core/Cargo.toml
[package]
name = "core"
[features]
default = []
gui = ["dep:gui-framework"]
cli = ["dep:clap"]
# cli/Cargo.toml
[dependencies]
core = { workspace = true, features = ["cli"] }
# gui/Cargo.toml
[dependencies]
core = { workspace = true, features = ["gui"] }
4. 版本发布策略
#!/bin/bash
# release.sh - 发布脚本
# 1. 更新版本号
cargo ws version patch
# 2. 按依赖顺序发布
cargo publish -p core
sleep 10 # 等待 crates.io 索引更新
cargo publish -p api
sleep 10
cargo publish -p cli
# 3. 创建 git 标签
git tag -a v$(cargo metadata --format-version 1 | jq -r '.workspace_members[0]' | cut -d' ' -f2)
git push --tags
常见问题和解决方案
1. 循环依赖
# 错误:A 依赖 B,B 依赖 A
# 解决:提取共同依赖到新的 crate
[workspace]
members = ["shared", "module-a", "module-b"]
# shared/Cargo.toml - 共同依赖
[package]
name = "shared"
# module-a/Cargo.toml
[dependencies]
shared = { path = "../shared" }
# module-b/Cargo.toml
[dependencies]
shared = { path = "../shared" }
2. 版本不一致
# 使用工作空间依赖确保版本一致
[workspace.dependencies]
serde = "1.0"
# 所有成员使用相同版本
[dependencies]
serde.workspace = true
3. 构建优化
# 根 Cargo.toml
[profile.release]
lto = true
codegen-units = 1
panic = "abort"
# 开发时的优化
[profile.dev.package."*"]
opt-level = 2
工作空间是管理大型 Rust 项目的强大工具,合理使用可以大大简化项目管理和依赖协调。