本节摘要:crate 是 Rust 的编译与发布单元,分库与二进制两种形态;Cargo 以清单文件驱动构建、依赖解析与发布。本节讲清单文件关键条目、依赖版本策略与 workspace 组织法。读完你能看懂并维护任何 Rust 项目的构建配置。
一个包可以同时含两者:清单里声明的库目标加若干二进制目标,测试目标自动生成。
[package] name = "court_archive" version = "0.3.1" edition = "2021" [dependencies] serde = { version = "1", features = ["derive"] } anyhow = "1" [dev-dependencies] pretty_assertions = "1" # 仅测试与示例使用
版本号语义:"1" 表示兼容 1.x 的最新版,锁定文件把每次构建钉在完全相同的版本上——锁定文件应当提交进版本库,保证所有人构建一致。features 是库的条件编译开关,serde 的 derive 打开后派生宏可用(第 9 章的用武之地)。
仓库长大会拆出多个 crate(核心库、CLI、协议、测试工具)。workspace 让它们共享一个锁定文件与构建目录:
[workspace] members = ["core", "cli", "proto"]
内部互相依赖写 path 即可,版本仍由统一锁定文件裁决。这是几乎所有知名 Rust 仓库的形态。

一个仓库装多个 crate,共享一个 Cargo.lock 与 target 目录,这是中型以上项目的标准装订方式。
# 根 Cargo.toml [workspace] members = ["parser", "storage", "cli"] resolver = "2" [workspace.dependencies] serde = "1" # 版本只声明一次
# cli/Cargo.toml [package] name = "cli" version = "0.1.0" edition = "2021" [dependencies] parser = { path = "../parser" } # 本地路径依赖 serde = { workspace = true } # 继承工作区版本 storage = { path = "../storage", optional = true } [features] default = ["persist"] persist = ["dep:storage"] # 特性开关控制依赖是否入场
特性体系是按需装订的核心:不带 persist 特性构建时 storage 及其传递依赖整个不编译。检查某个二进制为什么包含一段代码,cargo tree -f "{p} {f}" 列出依赖树与启用特性,是排查体积与编译时长的第一工具。
"1"、"1.2"、"1.2.3"、"=1.2.3"、"~1.2"、"^1.2" 各有不同的合法变动范围,默认无运算符等价于脱字符版。兼容性判定的责任在库作者:不兼容行为改动必须升主版本,否则下游锁定规则会静默引入行为变化。Cargo.lock 把解析结果钉死进版本控制,应用项目提交 lock、库项目通常不提交,这条约定俗成的分界要记牢。
| 目标 | 产物 | 席位 |
|---|---|---|
| bin | 可执行文件 | 每个一个 main |
| lib | rlib 供他 crate 链接 | 默认 src/lib.rs |
| example / test / bench | 各自独立目录 | 文档化样例与专项验证 |
同一包内 lib 与 bin 并存时,bin 用 use 包名::... 引 lib 的公开项——bin 自己也是一个独立 crate,这个认知能解释大量"为什么 main.rs 里找不到隔壁文件函数"的悬案。
单 crate 撑到多大都合法,但四个信号出现任意两个就该拆:编译时间明显拖慢迭代(拆分后可并行编译);出现只被部分功能使用的重依赖(拆出可裁剪的特性子包);类型开始出现循环引用的冲动(拆出共享基础层打断环);团队分工需要稳定接口(crate 边界即契约)。反例也要记:过早拆分让本该一次重构的改动横跨多包,version 帧率与 path 依赖的摩擦都真实存在。默认单 crate,信号说话。
| 拆分收益 | 拆分成本 |
|---|---|
| 并行编译提速 | 跨包改动多一道版本协商 |
| 依赖按需裁剪 | 公开项必须显式设计 |
| 边界即契约 | 小团队沟通成本上升 |
升级一个主版本依赖后编译失败,走查顺序固定五步:cargo update -p 包名 精确更新目标;读失败处的迁移说明(错误信息通常直指改名后的 API);用 cargo tree -i 旧类型所在包 查谁还在拉旧版本——多版本共存是升级期最隐蔽的坑,同名类型分属两包会报"期望与实际相同名字却不同类型";临时用精确版本回退保主干绿;最后补一个版本锁定注释说明原因。多版本共存问题的识别标志是报错里出现两条不同的路径版本号,看到它就直奔 cargo tree,不要在类型上浪费时间。
某项目同时依赖 A 1.0 与 A 2.0(不同传递路径引入),代码里两个"A 类型"互不兼容,报错显示相同名字却类型不符——这是版本共存的标准症状。处理次序:cargo tree -i A 看清两版各被谁拉入;优先给上游提 issue 或用补丁统一版本(workspace 的 patch 段);短期用精确版本把多数路径压到同一大版本;最后剩余的双版本共存若隔离良好(互不穿透边界)其实可以放行。这个判例的教训是收到"同名不同类型"报错时先查依赖树再改代码,方向对了十分钟结案。