23-Cargo 工作流
23 分钟阅读
Cargo 工作流 (Cargo Workflow)
面向 Rust 1.97.1 (stable, 2026-07)。如果你把 Cargo 只理解成“下载依赖的工具”,这一篇会帮你把它扩展成完整的日常工作流心智模型。
你会看到的不是一堆零散命令,而是“建项目、改依赖、加检查、发版本、做 CI”这些真实动作在 Cargo 里分别落到哪里。
Cargo 同时承担了包管理、构建、测试、文档、发布和 workspace 调度。对 Go 用户来说,差异最大的是:Cargo 把更多工程化决策显式做成了配置和命令入口。
Q1. cargo new 和 cargo init 有什么区别?
Tags: hot cargo new cargo init
适用版本: Cargo
一句话答案: cargo new 用来创建新目录并初始化项目,cargo init 用来把已有目录变成 Cargo 项目。
解答: 前者适合从零开项目,后者适合你已经有代码仓库、文档目录或空文件夹,只差把 Rust 工程骨架补进去。它们都会生成 Cargo.toml 和 src/ 结构,只是对现有目录的处理不同。
| |
cargo new 默认创建新目录;cargo init 会在当前目录或指定目录上操作,因此更适合把现有仓库接入 Cargo。
Go 对比:
- Go 通常是新建目录后自己
go mod init,再手工放源码。 - Cargo 把目录骨架和 manifest 一起生成,起步更完整。
- 已有仓库要接入 Rust 时,
cargo init的定位最像“补一个工程骨架”。
记忆点:
- 新目录:
cargo new。 - 已有目录:
cargo init。 --lib和--bin决定初始产物类型。
Q2. 日常最常用的 cargo build、run、check、test 怎么分工?
Tags: hot build check test
适用版本: Cargo
一句话答案: build 产出可执行文件,run 构建后运行,check 只做类型检查不产出最终二进制,test 则构建并执行测试目标。
解答: 对迭代开发来说,cargo check 往往是最快的反馈回路;只有当你需要真正运行程序、链接产物或验证基准行为时,再切到 cargo run / cargo build。
| |
cargo check 不会跳过借用检查和类型检查,它只是省去了后面的代码生成与链接阶段,因此经常比完整构建快很多。
Go 对比:
- Go 的
go test ./...和go build也承担检查与构建,但cargo check这个“只做到编译前半程”的命令更常用。 - Rust 编译成本更高,因此把
check当日常主循环非常划算。 - 改完代码先
check,准备交付前再test/build,是很常见的节奏。
记忆点:
- 写代码时先
cargo check。 - 要运行就
cargo run。 - 要产出制品或验证发布模式,就
cargo build --release。
Q3. Cargo.toml 和 Cargo.lock 各自记录什么?
Tags: hot Cargo.toml Cargo.lock
适用版本: Cargo
一句话答案: Cargo.toml 记录“你希望依赖什么”,Cargo.lock 记录“这次实际解析到了哪些精确版本”;二进制默认提交 lock,纯库通常不提交。
解答: Cargo.toml 更像需求声明,允许写版本范围、feature、profile 等;Cargo.lock 则是解析结果快照,用来让同一项目在不同机器和不同时间尽量复现同一依赖图。
| |
真实的 Cargo.lock 会带上精确版本、来源、checksum,以及该包解析出的 dependencies 列表,例如:
| |
Cargo.toml 里写 serde = "1" 只是范围;Cargo.lock 才钉死“这次编出来用的是哪一个精确版本、校验和是什么、还拉进了哪些传递依赖”。
默认提交建议:
| |
如果你只删 Cargo.lock 想“碰碰运气修问题”,很可能只是偷偷换了一套依赖组合,而不是解决真正根因。
Go 对比:
go.mod兼有需求声明和一部分最小版本信息,go.sum更偏校验摘要。- Cargo 把“声明”和“最终解析结果”分得更开。
- 这也是为什么 Rust 项目里
Cargo.lock的存在感通常比go.sum更强。
记忆点:
Cargo.toml是意图,Cargo.lock是带 checksum 的解析结果。- 二进制默认提交 lock;纯库通常不提交。
- 删 lock 文件不是常规修复手段。
Q4. Cargo.lock 到底要不要提交到仓库?
Tags: common Cargo.lock
适用版本: Cargo
一句话答案: 应用和二进制项目通常应该提交;发布到 crates.io 的纯库默认通常不提交;带示例/集成测试/workspace 的库仓库则建议提交。
解答: 对应用来说,提交 Cargo.lock 能显著提升构建可复现性,CI 和线上部署都更稳。对公开库来说,消费者最终仍会按自己的依赖图重新解析,因此发布到 crates.io 的纯库默认通常不提交 lock;但如果这个库仓库本身也带示例、工具、集成测试,或处在 workspace 里,提交 lock 反而更省心。
| |
默认可执行建议可以记成三条:
- 应用 / CLI / 服务:默认提交
Cargo.lock。 - 发布到 crates.io 的纯库:默认不提交
Cargo.lock。 - 库仓里已有示例、集成测试或 workspace:建议提交,并在团队里固定这一条。
更重要的是团队约定一致:别今天提交、明天又在 review 里把它删掉。对应用项目,默认提交通常是最省心的选择。
Go 对比:
- Go 没有一个和
Cargo.lock作用完全一致的文件。 - Rust 团队协作里,lock 文件更像正式制品的一部分,而不是临时缓存。
- 如果你做的是最终部署的软件,提交 lock 文件几乎总是更稳的选择。
记忆点:
- 应用项目默认提交
Cargo.lock。 - 纯库默认通常不提交;带示例/workspace 则建议提交。
- 把 lock 文件当缓存删掉,往往会制造新变量。
Q5. cargo add、cargo remove、cargo update 各做什么?
Tags: common dependencies
适用版本: cargo add/remove 内置于 Cargo 1.62+
一句话答案: add 改 manifest、remove 删依赖、update 刷新锁文件里的已解析版本。
解答: 这三者处理的是依赖生命周期的不同阶段。很多人会把 cargo update 误解成“升级工具链”,其实它只是重新解析依赖图并更新 Cargo.lock。
| |
当你只想升级某个包并观察影响时,cargo update -p name 比直接删 lock 文件可控得多。
Go 对比:
- Go 里你常用
go get既改模块需求又触发版本调整。 - Cargo 把这些动作拆得更细,因此意图更明确。
- 工具链升级不是
cargo update,那是rustup update的事。
记忆点:
- 改 manifest:
add/remove。 - 刷锁文件:
update。 - 局部升级优先
cargo update -p。
Q6. cargo install 为什么不是“给当前项目装依赖”?
Tags: common cargo install
适用版本: Cargo
一句话答案: 因为 cargo install 安装的是可执行 crate 到你的用户环境,不会修改当前项目的 Cargo.toml。
解答: 这和 cargo add 经常被初学者混淆。cargo install 适合装开发工具,比如 cargo-watch、cargo-nextest、just 之类;如果你想让当前项目能 use serde::...,应该用 cargo add serde。
| |
最后一条 cargo install --path . 的意思是“把当前 package 产出的二进制安装到用户环境里”,常用于本地体验 CLI。
Go 对比:
- Go 的
go install也偏向“安装一个可执行工具”,这一点和 Cargo 很像。 - 但 Go 用户刚转 Rust 时容易误以为
install会顺手改项目依赖,这点要特别分清。 - 想改项目依赖,一律先想到
cargo add。
记忆点:
- 项目依赖用
cargo add。 - 全局工具用
cargo install。 install不会改当前项目 manifest。
Q7. profile.dev 和 profile.release 该怎么理解?
Tags: common profile
适用版本: Cargo
一句话答案: profile 是构建配置模板,用来决定优化级别、调试信息、panic 策略等,而不是另一套独立工程配置。
解答: 开发阶段通常更关心编译速度和调试友好性,发布阶段则更看重体积、性能和行为一致性。Cargo 用 profile 把这些开关集中起来管理。
| |
最常见的实践是:默认 dev 跑测试和调试,需要测真实性能时再显式 cargo build --release 或 cargo run --release。
Go 对比:
- Go 的构建模式旋钮相对更少,很多优化决策不需要你手动显式配置。
- Cargo profile 给了更多控制面,因此也要求你更清楚自己是在“开发反馈回路”还是“发布制品回路”。
- 性能结论尽量基于 release 构建,不要在 dev 模式下下判断。
记忆点:
- profile 是构建参数集合。
- 调试看
dev,性能看release。 - 测性能时别忘了
--release。
Q8. feature 在命令行上怎么开、怎么关、怎么排查?
Tags: common features
适用版本: Cargo
一句话答案: 用 --features、--no-default-features、--all-features 控制;用 cargo tree -e features 看依赖图里 feature 是怎么被打开的。
解答: feature 问题常见于“为什么某个可选依赖突然被编进来了”或者“为什么我明明没开这个开关却表现像开了”。Cargo 的 feature 统一规则意味着你必须从整个依赖图角度排查,而不是只看当前 crate。
| |
如果一个 feature 被多个依赖间接开启,cargo tree -e features 往往比肉眼翻 Cargo.toml 快得多。
Go 对比:
- Go 没有 feature unification 这套常规工作流入口。
- Rust 项目里“先看依赖图,再猜行为”是很重要的排查习惯。
- 一旦 feature 设计成互斥,会在团队协作里不断制造麻烦。
记忆点:
- 开 feature:
--features。 - 关默认 feature:
--no-default-features。 - 排查来源:
cargo tree -e features。
Q9. workspace 日常最该记住哪些 Cargo 命令?
Tags: common workspace
适用版本: Cargo workspace
一句话答案: 先记住 --workspace、-p <package>、--exclude,它们分别对应“全仓跑”“只跑某个成员”“除了某些成员都跑”。
解答: workspace 一旦成员变多,最容易出现两种低效:要么每次全仓慢跑,要么忘了当前命令到底作用在哪个成员上。掌握这几个作用域开关后,命令意图会清楚很多。
| |
当 CI 和本地命令行为不一致时,先比对有没有 --workspace 或 -p 这类作用域差异。
Go 对比:
- Go 常见是
./...这种路径展开语义。 - Cargo 更偏向 package/workspace 维度调度,因此命令式更强。
- 大仓库里先选好作用域,再谈优化速度。
记忆点:
- 全仓:
--workspace。 - 单成员:
-p name。 - 先确定作用域,再跑命令。
Q10. CI 里最常见的 Cargo 检查流水线长什么样?
Tags: common CI
适用版本: Cargo
一句话答案: 最小实用流水线通常是 fmt、clippy、test 三件套,再按项目需要补 doc、bench 或多目标构建。
解答: 这套顺序的目的不是凑热闹,而是让“格式问题”“明显 lint 问题”“行为问题”尽量在不同阶段快速失败。很多团队还会先跑一次 cargo check --workspace 做快反馈,再跑更完整的测试矩阵。
| |
如果项目面向公开库,通常还会补一条 cargo doc --no-deps 看文档能否生成。
Go 对比:
- Go 团队常见是
gofmt、go vet、go test ./...。 - Rust 的对应物更分工明确,但整体思路完全类似:先静态检查,再行为验证。
- CI 慢的时候,先区分是构建目标太多、测试太多,还是每次都在重复做全仓工作。
记忆点:
- 常见最小流水线:
fmt+clippy+test。 --workspace --all-targets能覆盖得更全。- 先有稳定基线,再谈优化 CI 时长。
Q11. 内网或离线环境下,Cargo 依赖怎么稳定下来?
Tags: common vendor offline
适用版本: Cargo
一句话答案: 用 cargo vendor 把依赖拉到仓库内或制品库里,再通过 .cargo/config.toml 把 crates.io 重定向过去。
解答: 这在内网、可重复构建、长期归档场景里很常见。和只靠本机缓存不同,vendor 是显式把依赖来源纳入项目交付的一部分。
| |
| |
做完后最好再在干净环境里跑一次构建,确认没有偷偷依赖用户主目录下的旧缓存。
Go 对比:
- Go 也有
vendor/机制,但很多团队日常不常开。 - Cargo 在企业内网里使用 vendor 的存在感通常更高,因为依赖解析和构建复现常被放进交付要求里。
- 只说“我本机能编”不算离线方案,关键是别的机器能不能一样编。
记忆点:
cargo vendor是正式方案,不是临时 hack。- 配套
.cargo/config.toml才会真正走本地源。 - 离线构建要在干净环境复验。
Q12. 发布 crate 前,最小检查清单应该有哪些?
Tags: common publish
适用版本: Cargo
一句话答案: 至少确认 metadata 补齐、cargo publish --dry-run 能过、无残留 path 依赖、README 和许可证正确,再决定是否正式发布。
解答: 很多发布事故不是代码错,而是 manifest 信息不全、包内容打错、私有文件泄漏或依赖源不合法。--dry-run 的价值就在于尽量把这些问题挡在真正上传之前。
| |
| |
如果这是第一次对外发布,建议先看 cargo package --list 输出,确认最终被打包进去的文件真的是你想公开的那一批。
Go 对比:
- Go 模块发布更多依赖代码托管平台的 tag 和路径约定。
- Cargo 把发布动作集中到 crates.io 工作流里,因此 manifest 完整度更重要。
- 发布前检查打包内容,在 Rust 里和检查 API 本身一样重要。
记忆点:
- 正式发布前先
--dry-run。 - manifest 元数据要完整。
- path 依赖和错误打包内容是常见发布坑。
Q13. 依赖写成 ^1.2 / "1" 到底有多松?和 Cargo.lock 什么关系?
Tags: hot semver Cargo.lock
适用版本: Cargo
一句话答案: Cargo.toml 里的版本是兼容范围(有多松看写法),Cargo.lock 才钉死本次解析出的精确版本;有 lock 时构建可复现,改范围或 cargo update 才会换版本。
解答: Cargo 默认把 "1.2" 理解成 caret 需求 ^1.2,即允许 >=1.2.0, <2.0.0。写 "1" 等价 ^1,允许整个 1.x 最新兼容版本。看起来“只写了一个数字”,实际是一条范围,不是钉死补丁版。
| |
| |
应用提交了 Cargo.lock 时,别人 cargo build 会按 lock 复现,不会每次偷偷跳到范围内更新版本;想升级才跑 cargo update。纯库通常不提交 lock,下游会按自己的图在范围内再解析。
Go 对比:
- 有点像
go.mod里的版本需求 + 锁/求和校验的分工,但 Cargo 把“范围声明”和“解析快照”分得更开。 - 看到
"1"别当成“只用 1.0.0”。 - 可复现构建看 lock,不看 toml 里的短版本号。
记忆点:
"1"/"1.2"默认是^范围。- toml 管兼容窗口,lock 管精确复现。
- 升级依赖靠
cargo update,不是改完 toml 就自动漂。
Q14. cargo tree 怎么查出是谁引进了某个 crate?
Tags: common cargo tree
适用版本: cargo tree 随 Cargo 提供(老版本可用 cargo install cargo-tree)
一句话答案: 用 cargo tree -i <crate> 反查“谁依赖了它”;再配合 -e/--edges 和 feature 相关选项看清引入路径。
解答: 直接依赖在 Cargo.toml 里一眼能看见;传递依赖才需要 cargo tree。最常用的是 invert:从目标 crate 往上翻到你的包。
| |
读输出时注意:同一 crate 可能因 semver 不兼容出现多个版本;-i 会分别展示。若只在测试里出现,确认是否来自 [dev-dependencies]。排查“为什么编进了不想要的依赖”时,先 -i 定位引入边,再决定是换依赖、关 feature,还是 [patch]/[exclude] 类手段。
Go 对比:
- 类似
go mod why/go mod graph的“谁拉进来的”问题。 - Cargo 侧日常入口就是
cargo tree,尤其-i。 - 先查清引入路径,再改 toml,比盲目
cargo update有效。
记忆点:
- 反查用
cargo tree -i <name>。 - 分清直接依赖和传递依赖。
- 多版本并存时要对着 tree 看,不要猜。
Q15. build.rs 干什么用?和 go generate 像吗?
Tags: common build.rs
适用版本: Cargo
一句话答案: build.rs 是编译前由 Cargo 运行的构建脚本,用来探测系统、编译 C 库、codegen 或把结果告诉 rustc;和 go generate 部分像,但接入构建图更紧。
解答: 把 build.rs 放在 package 根目录后,Cargo 会在编译正式 crate 之前先编并执行它。脚本通过 println!("cargo:...") 指令与 Cargo 通信,例如设置 rustc-cfg、链接库、声明重跑条件。
| |
| |
典型用途:用 cc 编本地 C 代码、查 pkg-config、根据环境生成绑定。它不是日常业务逻辑入口;能放进正常 Rust 源码的,就别塞进 build.rs。
Go 对比:
- 像
go generate/ cgo 前置步骤:都在“正式编译前”做事。 - 不同点:
build.rs是 Cargo 一等公民,改输入会按rerun-if-*自动重跑。 - Go 更常显式跑 generate;Rust 则常“有
build.rs就会被 build 触发”。
记忆点:
build.rs= 编译前钩子。- 用
cargo:指令回传配置。 - 构建脚本依赖进
[build-dependencies]。
Q16. dev-dependencies / build-dependencies 和普通依赖差在哪?[patch] 何时用?
Tags: common dev-dependencies patch
适用版本: Cargo
一句话答案: 普通依赖进最终产物;dev-dependencies 只服务测试/示例/基准;build-dependencies 只服务 build.rs。[patch] 用于本地或临时替换依赖图里的某个 crate,不适合当长期“私服”。
解答: 三类依赖出现在不同编译目标里,乱放会导致测试专用 crate 被发布进库,或构建脚本工具被链进运行时。
| |
[patch] 常见场景:修上游 bug 时先挂 path/git fork、在 workspace 里调试尚未发布的版本。它改的是解析结果,不是新的依赖“种类”。发布到 crates.io 的库不能指望下游自动带上你的 patch;应用/workspace 内部自用更合适。长期私有源应配 registry/[source],而不是靠 patch 冒充。
Go 对比:
dev-dependencies有点像只在测试里用的模块,但 Go 不单独在 go.mod 里划这么清晰的三段。[patch]类似replace指令:本地替换、临时覆盖。- 替换能救急,但要记得上游合并后删掉 patch。
记忆点:
- deps / dev-deps / build-deps 服务不同阶段。
- 测试专用库别写进
[dependencies]。 [patch]是临时替换,不是正式依赖源。
Q17. 我没开某个 feature,为什么可选功能仍被编进?
Tags: hot features unification
适用版本: Cargo
一句话答案: 因为 Cargo 对同一 package 做 feature unification(feature 统一):依赖图里只要有一条边打开了该 feature,整图里这个 crate 的那一份就会带上它,不是“你本地没写就关着”。
解答: Feature 不是按“当前 crate 的 Cargo.toml 一行”单独编译多份再拼;同一 semver 兼容版本的 crate 在解析结果里通常只有一份,其启用 feature 是所有引用方的并集。所以你自己没写 features = ["foo"],只要某个传递依赖、workspace 成员、或默认 feature 间接打开了 foo,你也会编进对应代码。
| |
| |
排查顺序:cargo tree -e features 找启用边 → 判断是默认 feature、直接依赖,还是传递依赖 → 再考虑 --no-default-features、换依赖版本,或让上游别无条件开 feature。互斥 feature(开了 A 就不能开 B)在统一规则下特别容易踩坑,设计库时尽量避免。
Go 对比:
- Go 的 build tag / 条件编译更偏“按文件或按目标裁剪”,没有 Cargo 这种跨依赖图的 feature 并集。
- 在 Rust 里“我没开”只说明你自己的 manifest;最终以整图统一结果为准。
- 看到意外可选依赖进了二进制,先查 feature 图,别先猜编译器 bug。
记忆点:
- Feature 按依赖图并集统一,不是按单个
Cargo.toml隔离。 - 排查入口:
cargo tree -e features。 - 和 Q8 的命令行开关配合看。
Q18. 改 edition 到底影响什么?改了会不会换语言语义?
Tags: common edition
适用版本: Cargo / Rust editions(2015 / 2018 / 2021 / 2024)
一句话答案: edition 决定本 package 的默认语法与若干语言默认行为;它不是工具链版本号,也不自动升级依赖的 edition。
解答: 写在 Cargo.toml 的 edition = "2021"(或 "2024" 等)影响的是:哪些语法默认可用、部分关键字预留、路径/async 等历史差异的默认解读。换 edition 通常要靠 cargo fix --edition 一类迁移辅助,再人工确认。它不会:把 rustc/cargo 升到新版本、改写依赖 crate 的 edition、或替你打开不稳定 feature。
| |
| |
依赖可以继续是 2018/2021,你的包用 2024:链接时各编各的,这是 edition 设计目标之一。想用新稳定 API,靠的是 Rust 版本 / MSRV(Minimum Supported Rust Version,最低支持的 Rust 版本),不是只改 edition 字符串。
Go 对比:
- 有点像 Go 的
go 1.xx语言版本行,但 Cargo 把“语言 edition”和“安装的编译器版本”分得更开。 - 改 edition ≠
rustup update。 - 团队应约定 edition 与 MSRV,避免“能编过的机器编不过”。
记忆点:
edition= 本包语言默认,不是工具链版本。- 依赖可混用不同 edition。
- 新 API 看 Rust 版本 / MSRV,不单看 edition。
Q19. release 怎么瘦身?LTO 和 strip 怎么用?
Tags: common release LTO strip binary-size
适用版本: Cargo
一句话答案: 在 [profile.release] 里提高优化并做链接期裁剪:常用 lto(Link-Time Optimization,链接时优化)、codegen-units = 1、strip = true(或 "symbols"),必要时再加 panic = "abort";先测体积与性能,不要盲开全套。
解答: cargo build --release 默认已经比 dev 小且快,但还可继续压。lto = true(或 "thin")让链接阶段跨 crate 优化/内联,通常更小更快,编译更慢。strip 去掉符号表,对发布二进制很有效。codegen-units = 1 利于优化,代价是并行编译变差。
| |
| |
注意:strip/lto 会让崩溃栈、性能剖析更难;调试用的符号别和发布制品混为一谈。库 crate 的体积问题更多在下游应用侧体现;应用/CLI 才是这套 profile 的主战场。依赖与 feature 裁剪(见 Q8、Q17)往往比再拧半档 LTO 更划算。
Go 对比:
- Go 也有
-ldflags="-s -w"一类去符号手段;Rust 把更多旋钮放进 Cargo profile。 - 体积优化要兼顾编译时间和可调试性,和 Go 发布构建一样是工程权衡。
- 先
cargo tree/ 关无用 feature,再开 LTO。
记忆点:
- 瘦身三件套:
lto+strip+(可选)codegen-units = 1。 - 先裁依赖/feature,再拧 profile。
- 发布与调试 profile 分开想。
Q20. [workspace.dependencies] 是干什么的?成员怎么继承?
Tags: hot workspace dependencies
适用版本: Cargo workspace
一句话答案: 在 workspace 根把依赖版本(和常用 features)集中写一次;成员用 foo = { workspace = true } 继承,避免每个 crate 各写一套版本号。
解答: 多成员仓库最常见的痛是:serde 在 A 里是 1.0.190、B 里是 "1"、C 又开了不同 feature,升级时满仓搜。[workspace.dependencies] 把“用哪一版、默认开哪些 feature”收到根 Cargo.toml。
| |
| |
注意:workspace = true 继承的是根里那一份声明;成员本地再写 version = ... 通常没必要,也容易和“单一真相源”打架。日常改版本优先改根,再 cargo check --workspace。和 Q9 的作用域命令、Q16 的 deps/dev-deps 分工是正交的:这里管的是版本集中,不是依赖种类。
Go 对比:
- 有点像 Go workspace / 单模块里统一依赖版本的思路,但 Cargo 用显式
workspace = true继承。 - 别把
[workspace.dependencies]当成“自动给所有成员装上”——成员仍要自己声明需要哪些 crate。 - 升级依赖先改根,再全仓验证。
记忆点:
- 版本写在根
[workspace.dependencies]。 - 成员:
{ workspace = true }。 - 改一处,全仓对齐。
Q21. rust-version / MSRV 在 Cargo 工作流里怎么用?
Tags: common MSRV rust-version
适用版本: Cargo(rust-version 已稳定多年)
一句话答案: 在 [package] 写 rust-version 声明 MSRV(Minimum Supported Rust Version,最低支持的 Rust 版本);它主要是兼容承诺与工具提示,不是 rustup 自动切版本。细节与对照见 02-updating。
解答: 和 Q18 说的一样:edition ≠ 工具链版本。库/应用若承诺“至少支持到某版 rustc”,就在 manifest 里写清楚:
| |
工作流上记住三件事:
- 声明:
rust-version= MSRV 承诺。 - 开发锁链:团队日常用哪版 rustc,更常写在
rust-toolchain.toml(见 02-updating)。 - 验证:CI 应用 MSRV 工具链跑
cargo test(库作者尤其值得);resolver 3 还会在选依赖版本时更照顾rust-version(见 22-packages-crates-and-modules 相关讨论)。
它不会在你本机低于 MSRV 时魔法升级编译器;低了就编不过或工具报警,该 rustup update / 换 toolchain。
Go 对比:
- 类似
go.mod里的go 1.xx语言/工具线提示,但 Rust 常把“最低支持”和“当前开发锁链”拆成rust-version+rust-toolchain.toml。 - 改
edition解决不了“要用更新的标准库 API”。 - 对外库写清 MSRV,对内用 toolchain 文件对齐团队。
记忆点:
rust-version= MSRV 声明。- 详解交叉引用 02-updating Q10。
- 和
edition、toolchain 文件分开记。
Q22. cargo deny / cargo audit 是干什么的?CI 要不要加?
Tags: common cargo-deny cargo-audit CI security
适用版本: 外部 Cargo 子命令(cargo-audit / cargo-deny);与 rustc 版本无关
一句话答案:
cargo audit 对照 RustSec 公告扫 Cargo.lock 里的已知漏洞;cargo deny 更宽——公告之外还可管许可证、禁用 crate、来源(crates.io/git)等策略。安全敏感或要发布的项目,CI 里至少加一类(常从 audit 或 deny 的 advisories 检查起步)。
解答:
两者都不是 rustup 自带的核心子命令,需另行安装:
| |
日常用法示意:
| |
cargo-deny 常配一份 deny.toml(示意,按团队改):
| |
和 Q10 的 fmt / clippy / test 关系:那是正确性与风格基线;audit/deny 是供应链与合规层。CI 建议:
| 场景 | 建议 |
|---|---|
| 玩具 / 周末脚本 | 可先不加 |
| 团队服务 / 要上线 | 加 cargo audit 或 cargo deny check |
| 开源库 / 合规严 | deny(许可证 + 公告)更常见 |
| 锁文件常变 | 与 --locked 测试同一 PR 跑,避免「测过的图」和「扫的图」不一致 |
误报或尚未升级的传递依赖:用工具自己的 ignore / 例外机制记原因,别整段关掉检查。
Go 对比:
- Go 怎么做:
govulncheck、依赖许可证扫描、私有代理策略。 - Rust 为什么不同:生态标准答案常落在
cargo-audit/cargo-deny,而不是语言自带命令。 - Go 程序员易踩的坑:以为
clippy已覆盖 CVE——没有;漏洞库要单独扫。
记忆点:
audit≈ 漏洞公告;deny≈ 公告 + 许可证 + 策略。- CI:上线项目建议加;与 Q10 检查流水线并列,不互相替代。
Q23. 编译太慢:sccache / mold / lld 怎么加速?
Tags: hot sccache mold lld link CI
适用版本: 外部工具;平台相关
一句话答案:
sccache(或 RUSTC_WRAPPER)缓存编译对象,适合 CI 与多 crate 工程;mold / lld 换更快链接器,常能明显缩短 release 链接时间。对标 Go 较少「换链接器」心智,但 Rust 大项目很常见。
解答:
| |
| |
| |
注意:链接器与 wrapper 是本机/CI 配置,一般不写进库的强制依赖。Windows 上按平台文档选 lld 等。
Go 对比:
- Go 编译通常更快,较少谈 sccache;CI 仍有模块缓存。
- Rust 为什么不同:单态化与 LLVM 更重,缓存/链接器收益大。
- Go 程序员易踩的坑:只加 CPU、不改链接器,release 仍卡在 link。
记忆点:
- 重复编译 → sccache。
- release 链接慢 → mold/lld。
Q24. cargo nextest 和默认 cargo test 差在哪?
Tags: common nextest CI test-runner
适用版本: cargo-nextest 外部工具
一句话答案:
cargo-nextest 是更快、隔离更好的测试运行器:默认每测一进程、调度更聪明、失败重跑与报告更友好。日常可用 cargo test;大仓库/CI 常换 nextest。
解答:
| |
| |
| |
与 24-testing 正交:测什么怎么写在 24;用哪个 runner 在本题。
Go 对比:
- 仍是
go test为主;Rust 社区 nextest 很常见。 - Go 程序员易踩的坑:以为必须改
#[test]属性才能用 nextest。
记忆点:
- 大项目 CI → 考虑
cargo nextest run。 - 测试写法不变。