入门 The Cargo Team 2026-09-13 15:48:23 · 3 阅读

第71章 常见问题(FAQ)

计划使用 GitHub 作为软件包仓库吗?

不。Cargo 的计划是像 npm 使用 npmjs.com 或 Rubygems 使用 rubygems.org 那样,使用 crates.io

我们计划永久支持将 Git 仓库作为包来源,因为它们可用于早期开发和临时补丁,即使人们主要依赖注册表获取包。

为什么要构建 crates.io 而不是将 GitHub 用作注册表?

我们认为支持多种包下载方式非常重要,包括从 GitHub 下载以及将包直接复制到项目包中。

话虽如此,我们认为 crates.io 提供了一些重要优势,很可能成为人们使用 Cargo 下载包的主要方式。

先例来看,Node.js 的 npm 和 Ruby 的 bundler 均支持中央注册表模式和基于 Git 的模式。在这些生态系统中,大多数包通过注册表下载,但也有相当一部分包使用基于 Git 的方式。

中央注册表在其他语言中流行的部分优势包括:

  • 可发现性。中央注册表提供了查找现有包的便捷途径。结合标签功能,注册表还可以提供生态系统范围的信息,例如最受欢迎或依赖最多的包列表。
  • 速度。中央注册表允许快速高效地仅获取包的元数据,然后仅下载已发布的包,避免仓库中存在的其他冗余内容。这显著提升了依赖项解析和获取的速度。随着依赖关系图扩大,下载所有 Git 仓库会迅速导致性能瓶颈。此外,并非每个人都拥有高速、低延迟的互联网连接。

Cargo 可以处理 C 代码(或其他语言)吗?

可以!

Cargo 负责编译 Rust 代码,但我们也知道许多 Rust 包都链接了 C 代码,而且过去几十年里已经为其他语言积累了一套成熟的编译工具链。

我们的解决方案:允许包在调用 rustc 之前运行一个由 Rust 编写的脚本。利用 Rust 来实现平台特定的配置,并在各包之间复用通用的构建逻辑。

能否在 make(或 ninja 等)内部使用 Cargo?

可以。虽然我们的初衷是让 Cargo 能够作为独立工具来编译顶层 Rust 包,但我们知道也有人希望从其他构建工具中调用 Cargo。

我们在设计上充分考虑了这种场景,注重错误码机制和机器可读的输出模式。这些方面仍有一些工作需要完善,但让 Cargo 能良好地融入传统脚本环境是我们的初衷,并会持续优先考虑。

Cargo 是否支持多平台包或交叉编译?

Rust 本身提供了根据平台配置代码部分的机制。Cargo 也支持平台特定的依赖,并计划在未来增加 Cargo.toml 中更细粒度的平台配置选项。

从长远来看,我们正在研究如何让用户更便捷地通过 Cargo 进行交叉编译。

Cargo 是否支持环境区分,例如 productiontest

我们通过配置档案(profiles)来支持环境区分:

  • 环境特定的编译参数(如开发环境使用 -g --opt-level=0,生产环境使用 --opt-level=3)。
  • 环境特定的依赖项(如测试用的 hamcrest 断言库)。
  • 环境特定的 #[cfg] 配置。
  • cargo test 命令。

Cargo 在 Windows 上能用吗?

可以!

Cargo 的所有提交都必须通过 Windows 上的本地测试套件。如果你在 Windows 上遇到问题,我们认为这是 bug,请提交 issue

为什么要把 Cargo.lock 纳入版本控制?

虽然 cargo new 默认会把 Cargo.lock 纳入版本控制,但要不要这么做取决于你的包的实际需求。

Cargo.lock 锁定文件的作用是记录一次成功构建时的依赖状态。Cargo 通过锁定文件保证在不同时间、不同系统上构建结果的一致性,确保使用的依赖及其版本与最初生成 Cargo.lock 时完全相同。

确定性构建有助于:

  • git bisect 定位 bug 的根源
  • 确保 CI 只因新提交而失败,而不是外部因素
  • 减少贡献者之间或与 CI 行为不一致带来的困惑

这份依赖快照在项目需要基于一致的依赖版本进行验证时也很有用,比如:

  • 验证最低支持的 Rust 版本(MSRV)低于某个依赖最新版本所支持的情况
  • 验证不受兼容性保证约束的人类可读输出(例如对错误信息做快照测试,确保其“易于理解”,这种标准太模糊,无法自动化)

不过,这种确定性可能带来虚假的安全感,因为 Cargo.lock 并不影响你的包的消费者,起作用的只有 Cargo.toml。例如:

锁定文件也可能成为合并冲突的来源。

关于通过 CI 验证更新版本依赖的策略,参见 Verifying Latest Dependencies

库的依赖可以使用 * 作为版本号吗?

自 2016 年 1 月 22 日起,crates.io 拒绝发布包含通配符依赖约束的所有包(不仅仅是库)。

虽然库在技术上可以这样做,但不应这么做。* 版本要求意味着“能与任何版本兼容”,这在现实中从不成立。库应始终明确指定其实际兼容的版本范围,即使是像“所有 1.x.y 版本”这样宽泛的范围也行。

为什么叫 Cargo.toml

作为与 Cargo 最频繁的交互之一,配置文件为何名为 Cargo.toml 的问题时常出现。首字母大写 C 是为了确保该文件在目录列表中与其他类似的配置文件归类在一起。文件排序时,大写字母通常排在小写之前,这保证了 MakefileCargo.toml 等文件会聚集在附近。后缀 .toml 则强调该文件采用 TOML 配置格式

Cargo 不允许使用 cargo.tomlCargofile 等其他名称,以强调识别 Cargo 仓库的便捷性。历史上,提供多种可选名称曾导致混淆:处理了其中一种情况,却意外遗漏了其他情况。

Cargo 如何离线工作?

--offline--frozen 标志指示 Cargo 不访问网络。如果操作需要联网,它会返回错误。您可以在一个项目中使用 cargo fetch 在离线前下载依赖项,然后在另一个项目中复用这些相同的依赖项。请参考配置值以通过 Cargo 配置进行设置。

本地化依赖(Vendoring)也与离线功能相关,更多详情请参阅源替换文档。

为什么 Cargo 正在重新构建我的代码?

Cargo 负责项目中 crate 的增量编译。这意味着如果你连续运行两次 cargo build,第二次不应该重新编译来自 crates.io 的依赖项。但实际情况中总会遇到 bug,Cargo 有时会在你没预期的情况下重新编译代码!

我们一直希望改善这方面的诊断信息,但不幸的是,这个问题已经很久没有进展了。在此期间,你可以通过设置 CARGO_LOG 环境变量来调试重新编译的问题:

$ CARGO_LOG=cargo::core::compiler::fingerprint=info cargo build

这会让 Cargo 输出大量关于诊断和重新编译的信息。其中往往包含项目被重新编译的线索,尽管你可能需要自己根据这些线索进行分析,因为目前的输出可读性并不算好。请注意,CARGO_LOG 必须在你认为不应该重新编译但实际发生了重新编译的那次构建命令中设置。不幸的是,Cargo 目前无法事后再调试“为什么刚才那个被重新编译了?”

历史上我们发现过一些会导致 crate 被重新编译的问题,包括:

  • 构建脚本打印了 cargo::rerun-if-changed=foo,但 foo 是一个不存在的文件,且没有任何东西会生成它。在这种情况下,Cargo 会一直运行构建脚本,以为它会生成该文件,但实际上并没有。解决方法是在这种场景下避免打印 rerun-if-changed

  • 连续的两次 Cargo 构建可能会因为某些依赖项启用的 features 集不同而产生差异。例如,如果第一次构建命令构建了整个 workspace,而第二次命令只构建其中一个 crate,这可能导致 crates.io 上的某个依赖项启用的 features 集不同,从而引发它及其所有依赖者被重新编译。遗憾的是目前并没有很好的解决办法,但如果在可能情况下,最好确保无论你在 workspace 中构建什么,某个 crate 启用的 features 集都是固定的。

  • 某些文件系统在时间戳方面表现异常。Cargo 主要依赖文件的时间戳来判断是否需要重新编译,但如果你用的是非标准文件系统,它可能会以某种方式影响时间戳(比如截断、造成漂移等)。遇到这种情况,欢迎提交 issue,我们可以看看能否对该文件系统做一些适配。

  • 并发的构建进程在删除产物或修改文件。有时你可能有某个后台进程在构建或检查项目。这些后台进程可能会意外删除构建产物或触碰文件(也可能纯属误操作),导致看似毫无缘由的重编译!最好的解决办法是管好这些后台进程,避免和你的工作冲突。

如果排查之后问题依旧,欢迎提交 issue

“版本冲突”是什么意思,如何解决?

failed to select a version for x which could resolve this conflict

见过上面这条错误信息吗?

这是 Cargo 用户最头疼的错误之一。多种情况都可能引发版本冲突,下面我们逐一分析可能的原因,并提供相应的排查方法:

  • 项目及其依赖通过 links 重复链接同一个本地库。Cargo 禁止两个包链接相同的原生库,即使隔着多层依赖也不允许。这种情况下错误信息会提示:Only one package in the dependency graph may specify the same links value,你需要手动检查并删除重复的 link 值。社区也有相应的约定来缓解这个问题。

  • 当项目中依赖的不同 crate 使用同一个依赖库,但版本号被限制导致无法确定正确的版本时,也会引发冲突。错误信息会提示:all possible versions conflict with previously selected packages。你可能需要修改版本要求以保持一致。

  • 如果项目中有多个版本的依赖,在使用 direct-minimal-versions 时,若无法满足最低版本要求,将导致冲突。你可能需要调整直接依赖的版本要求,使其满足相应的最低 SemVer 版本。

  • 如果依赖的 crate 不包含你选择的功能(features),也会引发冲突。此时需要检查依赖版本及其功能支持情况。

  • 在合并分支或 PR 时可能会出现冲突。如果冲突较为复杂,你可以重置所有“你的”更改,解决分支内的其他冲突,然后运行某个 cargo 命令(如 cargo treecargo check)。这会根据你本地的更改重新更新 lockfile。如果你之前在该分支中运行过某些 cargo update 命令,可以在此时重新执行它们。社区一直在寻求使用自定义合并工具来解决 Cargo.lockCargo.toml 的合并冲突。

为什么我的构建占用这么多空间?

Cargo 通过以磁盘空间换取更快的构建速度,包括以下几点:

  • 维护缓存以存储中间构建产物,避免在修改单个包时重新构建所有内容
  • 为不同的工具链版本、包版本、功能等组合维护独立的缓存条目,避免在配置切换时重新构建包
  • 为本地包启用增量编译,以加快对已更改包的重新构建速度
  • 如果你使用调试器,在 dev 配置 中启用 debuginfo

评论 (0)