2、Cargo项目管理:创建项目、项目结构、依赖管理、构建与运行、workspace

聊到 Rust 项目,Cargo 是绕不开的。说实话,我见过不少新手直接拿 rustc 编译单个文件,然后问为什么项目一复杂就崩了。嗯,Cargo 就是来解决这个问题的。它不只是包管理器,更是 Rust 生态的基石。今天我把 Cargo 的核心用法掰开揉碎讲清楚。

2.1 创建项目:cargo new 的隐藏细节

创建项目很简单,一条命令搞定:

cargo new my_project
cd my_project

但这里有个坑。我刚开始用 Rust 时,以为 cargo new 只是建个文件夹。其实它做了三件事:生成标准目录结构、初始化 Git 仓库、创建 Cargo.toml。如果你在已有 Git 仓库里执行,它会跳过 git init,避免嵌套。

如果你想创建库项目而不是可执行文件,加个 --lib 参数:

cargo new my_lib --lib

区别在哪?--lib 生成的是 src/lib.rs,而默认生成 src/main.rs。我个人习惯是:只要项目可能被其他项目引用,就用 --lib。哪怕你最终要生成二进制,也可以从 lib 开始,然后在 main.rs 里调用 lib 的逻辑。这样测试和复用都方便。

小技巧: 如果你在公司内网或代理环境下,记得设置 cargo new --registry 指定私有 registry。我曾经因为没配这个,导致 CI 里拉依赖拉了半天。

2.2 项目结构:Cargo 的约定优于配置

Cargo 对项目结构有明确的约定。你不需要告诉它「源码放哪」,它自己知道。标准结构长这样:

my_project/
├── Cargo.toml      # 项目元数据和依赖
├── Cargo.lock      # 锁定依赖版本(自动生成)
├── src/
│   ├── main.rs     # 入口文件
│   └── lib.rs      # 库入口(如果有 --lib)
├── tests/          # 集成测试
├── benches/        # 基准测试
├── examples/       # 示例代码
└── target/         # 编译产物(别手动改它)

为什么这么设计?说白了,就是让所有 Rust 项目长得一样。你换一家公司,打开项目还是这个结构。我参与过一个大型微服务项目,十几个 crate 全是这个结构,新人上手毫无障碍。

这里有个容易忽略的点:Cargo.lock 文件。对于可执行项目,一定要把它提交到 Git。对于库项目,反而建议忽略它。为什么?因为可执行项目需要锁定所有依赖的精确版本,保证构建可复现。而库项目应该允许使用者灵活选择依赖版本。

注意: 永远不要手动修改 target/ 目录下的文件。我曾经手贱删了 target 里的某个缓存文件,结果整个项目重新编译了半小时。Cargo 会管理好它,你只管跑 cargo build

2.3 依赖管理:Cargo.toml 的三种写法

依赖管理是 Cargo 的核心能力。在 Cargo.toml[dependencies] 段里,你可以用三种方式声明依赖:

方式 示例 适用场景
crates.io 版本 serde = "1.0" 最常用,从官方仓库拉取
Git 仓库 serde = { git = "https://github.com/serde-rs/serde" } 依赖未发布到 crates.io 的库
本地路径 my_lib = { path = "../my_lib" } 本地开发调试,多项目联调

我个人最常用的是版本号方式。但遇到需要修 bug 的场景,我会临时切到 Git 分支或本地路径。比如有一次我发现某个库有性能问题,直接 fork 到本地改完,用 path 依赖测试通过后再提 PR。

版本号写法也有讲究:

  • "1.0" 表示 >=1.0.0, <2.0.0,兼容语义化版本
  • "=1.0.0" 精确锁定版本
  • "1.0.*" 只允许补丁版本变化

你想想看,如果团队里有人写了 "*" 作为版本号,那每次构建都可能拉不同版本,迟早出事。我建议至少指定主版本号和次版本号。

2.4 构建与运行:cargo build 的三种模式

Cargo 的构建命令很简单,但背后有玄机。三种模式对应不同场景:

cargo build           # 调试模式,默认
cargo build --release # 发布模式,开启优化
cargo build --profile test # 测试模式(一般不用手动跑)

调试模式编译快,但运行慢。发布模式编译慢,但运行快。我刚开始时总用 --release 跑开发,结果每次改一行代码等两分钟。后来学乖了:开发用默认模式,只有做性能测试或准备发布时才用 release。

运行项目更简单:

cargo run            # 编译并运行
cargo run -- arg1    # 传递命令行参数
cargo run --release  # 以发布模式运行

这里有个小细节:cargo run 会自动检测源码变化,只重新编译修改过的文件。Cargo 的增量编译做得不错,但如果你改了 Cargo.toml 里的依赖,它会重新解析整个依赖树,那就会慢一些。

核心要点: 日常开发用 cargo buildcargo run,CI/CD 里用 cargo build --release。别搞反了。

2.5 Workspace:管理多 crate 项目的利器

当项目大到一定程度,你会想把代码拆成多个 crate。比如一个 Web 服务,可以拆成 apidbmodels 三个 crate。Workspace 就是干这个的。

结构示例:

my_workspace/
├── Cargo.toml          # workspace 根配置
├── crates/
│   ├── api/
│   │   ├── Cargo.toml
│   │   └── src/
│   ├── db/
│   │   ├── Cargo.toml
│   │   └── src/
│   └── models/
│       ├── Cargo.toml
│       └── src/
└── Cargo.lock

根目录的 Cargo.toml 这样写:

[workspace]
members = [
    "crates/api",
    "crates/db",
    "crates/models",
]

然后每个子 crate 有自己的 Cargo.toml,可以互相引用:

# crates/api/Cargo.toml
[dependencies]
db = { path = "../db" }
models = { path = "../models" }

Workspace 的好处是什么?我总结三点:

  • 共享一个 Cargo.lock:所有 crate 用同一套依赖版本,避免冲突
  • 统一构建:在根目录执行 cargo build,所有子 crate 一起编译
  • 并行编译:Cargo 会自动并行编译不依赖的 crate,速度更快

我曾经在一个项目里见过 20 多个 crate 的 workspace。刚开始觉得麻烦,但后来发现每个 crate 的编译时间只有几秒,改一个 crate 不会影响其他 crate 的缓存。这种模块化带来的好处,远大于管理成本。

避坑指南: Workspace 里的子 crate 不要重复声明相同的依赖。比如两个子 crate 都依赖 serde,版本号必须一致,否则 Cargo 会报错。我曾经因为版本号写的不一致,排查了半天才发现是 1.0.01.0.1 的差异。

2.6 知识体系总览

下面这张图概括了 Cargo 项目管理的核心逻辑:

Cargo 项目管理 创建项目 cargo new / --lib 项目结构 src/ tests/ Cargo.toml 依赖管理 版本/Git/路径 构建与运行 build / run / --release Workspace 多 crate 统一管理 核心原则:约定优于配置,版本锁定,模块化 日常开发用 debug,CI/CD 用 release

这张图把 Cargo 的五个核心能力串起来了。你从创建项目开始,理解结构,管理依赖,构建运行,最后用 workspace 组织大型项目。每一步都有 Cargo 帮你兜底。

好了,这一章的内容就到这里。Cargo 是 Rust 开发者的好伙伴,多用几次就熟了。记住:遇到问题先查 cargo --help,或者直接 cargo doc --open 看本地文档。


公众号:蓝海资料掘金营,微信deep3321