引言:为什么在 Rust 中需要单一代码库?
当你开始学习 Rust 时,首先遇到的是通过 cargo new 创建的单个项目。但是,当你的项目规模扩大时该怎么办:出现了公共库、多个可执行文件(例如,服务器和客户端),或者你想将重复的代码提取到单独的包中?
这时,单一代码库(monorepo) 就派上了用场——这是一种将多个相互关联的项目(库和可执行文件)的代码存储在一个仓库中的方法。Rust 为此提供了一个内置解决方案——Cargo Workspaces(工作空间)。
在本文中,我们将从零开始讲解如何在 Rust 中组织单一代码库:从文件夹结构到实际示例。你将学习如何正确地将代码划分为模块、连接本地依赖项以及管理构建过程。
1. 什么是 Cargo Workspace 以及如何创建它?
工作空间(workspace)是一组 crate,它们共享同一个 Cargo.lock 文件和公共的 target 目录。这可以避免依赖项版本冲突并加快编译速度。
创建单一代码库结构
让我们创建一个包含两个 crate 的简单单一代码库:库 utils 和可执行文件 app。
mkdir my_monorepocd my_monorepocargo new utils --libcargo new app现在,创建声明工作空间的根级 Cargo.toml 文件:
[workspace]members = [ "utils", "app",]重要提示: 确保在 utils/Cargo.toml 和 app/Cargo.toml 文件中没有包含 workspace 字段的 [package] 部分——这是不需要的。Cargo 会自动识别它们是工作空间的一部分。
项目结构
创建后,你将得到如下结构:
my_monorepo/├── Cargo.toml # 根级 workspace├── utils/│ ├── Cargo.toml│ └── src/│ └── lib.rs└── app/ ├── Cargo.toml └── src/ └── main.rs2. 代码组织:库和模块
现在,让我们向 utils 库添加代码。假设我们想创建用于处理数字和字符串的通用函数。
库代码 (utils/src/lib.rs)
pub mod math;pub mod strings;创建模块文件:
// utils/src/math.rspub fn add(a: i32, b: i32) -> i32 { a + b}
pub fn multiply(a: i32, b: i32) -> i32 { a * b}// utils/src/strings.rspub fn greet(name: &str) -> String { format!("你好, {}!", name)}
pub fn to_uppercase(text: &str) -> String { text.to_uppercase()}在可执行文件中连接库
在 app/Cargo.toml 文件中,指定对本地 crate 的依赖:
[package]name = "app"version = "0.1.0"edition = "2021"
[dependencies]utils = { path = "../utils" }现在,在 app/src/main.rs 中,我们可以使用 utils 中的函数:
use utils::math;use utils::strings;
fn main() { let sum = math::add(5, 10); println!("5 + 10 = {}", sum);
let greeting = strings::greet("世界"); println!("{}", greeting);
let upper = strings::to_uppercase("rust rocks"); println!("{}", upper);}从单一代码库的根目录使用以下命令运行项目:
cargo run -p app你将看到输出:
5 + 10 = 15你好, 世界!RUST ROCKS3. 扩展组织:多个可执行文件
在单一代码库中,经常需要包含多个