7.2 Crates与Cargo:案卷装订


7.2 Crates 与 Cargo:案卷装订

本节摘要:crate 是 Rust 的编译与发布单元,分库与二进制两种形态;Cargo 以清单文件驱动构建、依赖解析与发布。本节讲清单文件关键条目、依赖版本策略与 workspace 组织法。读完你能看懂并维护任何 Rust 项目的构建配置。

两种形态

  • 库 crate:给别人用的案卷汇编,产出被别人链接;
  • 二进制 crate:可执行文件,必须有 main。

一个包可以同时含两者:清单里声明的库目标加若干二进制目标,测试目标自动生成。

清单文件要点

[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 章的用武之地)。

workspace:多案卷并档

仓库长大会拆出多个 crate(核心库、CLI、协议、测试工具)。workspace 让它们共享一个锁定文件与构建目录:

[workspace] members = ["core", "cli", "proto"]

内部互相依赖写 path 即可,版本仍由统一锁定文件裁决。这是几乎所有知名 Rust 仓库的形态。

图:包结构与产物

图:包结构与产物

要点回顾

  • crate 是编译与发布单元,包可同时产出库与二进制;
  • 锁定文件保证构建可复现,务必入库;
  • 语义化版本加 features 是依赖管理的两个主要旋钮;
  • workspace 是长大的仓库的标准答案:一份锁定,多 crate 协作。

Workspace:多案卷合订本

一个仓库装多个 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 撑到多大都合法,但四个信号出现任意两个就该拆:编译时间明显拖慢迭代(拆分后可并行编译);出现只被部分功能使用的重依赖(拆出可裁剪的特性子包);类型开始出现循环引用的冲动(拆出共享基础层打断环);团队分工需要稳定接口(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 段);短期用精确版本把多数路径压到同一大版本;最后剩余的双版本共存若隔离良好(互不穿透边界)其实可以放行。这个判例的教训是收到"同名不同类型"报错时先查依赖树再改代码,方向对了十分钟结案。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U