常见问题
有计划使用 GitHub 作为包存储仓库吗?
没有。Cargo 会使用 crates.io,就像 npm 的 npmjs.com 和 Rubygems 的 rubygems.org 。
我们将永远支持使用 git 仓库作为包的一个来源,因为其可以用在早期开发和临时覆盖中,但还是会将注册中心作为包的主要来源。
为什么建立 crates.io 而不是使用 GitHub 作为 registry ?
我们认为支持多种下载包的方式是很重要的,其中包括从 GitHub 下载并将其拷贝到你的包中。
尽管如此,我们还是认为 crates.io 提供了一些重要的便利,会成为人们用 Cargo 下载包的主要方式。
作为先例,Node.js 的 npm 和 Ruby 的 bundler 都同时支持一个中心化的注册中心模型和基于 git 的模型,大部分的包都是从它们生态的注册中心下载的,少数的包使用基于 git 的下载。
让中心化的注册中心在这些语言中得以流行的优势有:
- 可发现性。核心的注册中心提供了便捷的地方来查找已有的包。结合标记技术,注册中心可以提供生态广泛的信息,诸如最受欢迎或最被依赖的包的列表。
- 速度。核心的注册中心可以让我们快速有效地获取包的元数据,然后高效地只下载那些被发布的包,而不会下载那些恰巧保存在 git 仓库中的无用内容。这能显著提高依赖解析和下载的速度。随着依赖图的增大,从 git 仓库中下载的方式很快就会陷入困境。另外不是每个人都拥有高速低延迟的网络。
Cargo 可以和 C 代码(或者其他语言)一起工作吗?
当然! Cargo 用于处理编译 Rust 代码,但我们也了解很多 Rust 包链接到 C 代码,编译其他语言的工具已经发展了几十年的时间。
我们的解决方案是:Cargo 允许包指定(用Rust写的)脚本,在调用 rustc
之前执行。用于实现平台特定的设置和重构包之间的公共构建的功能。
Cargo 可以用在 make
(或 ninja
,...) 之中吗?
当然。在我们设计让 Cargo 独立编译 Rust 包,也考虑到一些人想要在其他构建工具中调用 Cargo。
我们设计让Cargo在这些情境下很好地工作,花了很多精力在错误代码和机器可读的输出上。 这些方面我们还有很多工作要做,在常见的脚本中使用Cargo是从一开始就设计好的,并且会继续作为高优先级的任务。
Cargo 可以处理多平台的包或者交叉编译吗?
Rust 本身提供了基于平台控制代码段的功能。Cargo 也支持 平台特定依赖,而且我们计划在 Cargo.toml
中支持为不同平台进行更多设置。
长期来看,我们正在研究用 Cargo 进行交叉编译的简便方法。
Cargo 是否支持环境,比如 production
或 test
?
我们通过使用编译设置(profiles)来支持环境:
- 环境指定标志(如
-g --opt-level=0
用于开发环境,而--opt-level=3
用于生产环境)。 - 环境指定依赖,如
hamcrest
用于测试断言。 - 环境指定
#[cfg]
。 cargo test
命令。
Cargo 可以在 Windows 上使用吗?
当然!
Cargo 的所有 commits 提交都要求通过 Windows 上的测试。如果你在 Windows 上运行 Cargo 时遇到问题,我们将其视为一个 bug,请提一个 issue。
为什么二进制 crate 有 Cargo.lock
,而库 crate 却没有?
Cargo.lock
的目的在于描述一次成功构建发生时,当时完整的状态。Cargo 借助 lockfile 在不同时刻和不同系统中提供确定性的构建结果,保证使用的依赖版本与 Cargo.lock
被创建时使用的完全一致。
这种特性最适合应用 (application) 以及那些处于依赖链末端的包 (二进制crate) 。因此,建议每个二进制 crate 都添加 Cargo.lock
。
对于库而言情况就有所不同了。一个库不仅仅是库的开发者在用,而会是下游的所有使用者。
依赖该库的用户不会检查这个库的 Cargo.lock
(即使这个文件存在)。这是应该的,因为对于库的用户来说,一个库不应该被确定性地重新构建。
如果一个库被几个依赖传递性地使用,那么应该只保留该库的一份拷贝 (SemVer兼容的前提下)。
如果 Cargo 使用所有依赖的 Cargo.lock
,就可能会使用到该库的多个版本,甚至造成版本冲突。
换句话说,库指定了自己的依赖的 SemVer 语义化版本,但是无法看到(依赖图的)全貌。 只有像是二进制程序这样的末端产品才能看到全貌,决定使用依赖的哪个具体版本。
库可以使用 *
来指定依赖的版本吗?
从2016年1月22日开始,crates.io 拒绝所有带着 *
依赖的包(不仅是库)。
库可以,但是,库不应该可以。一个 *
版本请求好像在说:"能在任何版本都会正常工作",但这是不可能的。库总是应该指定可以工作的一个版本范围,即使是"所有的 1.x.y 版本" 这种宽泛的范围。
为什么有 Cargo.toml
?
作为与 Cargo 交互最多的部分,关于为什么配置文件叫 Cargo.toml
的问题从来没有停过。开头的大写字母 C
是为了让这个配置清单文件和其他类似的配置文件(configuration file)放在一起。
文件排序一般会把大写字母开头的文件放在小写字母开头的文件之前,这保证 Cargo.toml
会和 Makefile
这类文件放在一起。后面的 .toml
表示这是 TOML 格式 的文件。
Cargo 不允许使用其他的配置文件名如 cargo.toml
、Cargofile
,从而更容易识别 Cargo 仓库。提供可选的其他名字在历史上经常导致某些情况被忘记处理,从而导致错误。
如何在离线状态使用 Cargo ?
Cargo 经常被用于限制或者没有网络的场景,比如在飞机上、CI 环境、或者嵌入到大型的产品部署中。当 Cargo 尝试从网络获取资源时,用户经常感到惊讶,因此经常要求 Cargo 可以在离线环境中使用。
Cargo 其实不会主动去访问网络,除非你叫它去做。也就是说,如果不需要来自 crates.io 、 git 仓库或其他网络位置的 crate 时,Cargo 绝不会去访问网络。 反过来说,如果 Cargo 尝试访问网络,那一定是它需要通过网络来获取所需的资源。
Cargo 会十分激进地缓存信息以最少化网络访问。它保证,如果 cargo build
(或其他类似的指令) 执行成功,那么下一次 cargo build
绝不会再访问网络,除非 Cargo.toml
在这期间被修改。阻止网络访问的方法归结为 Cargo.lock
文件以及相对应的对 crate 的 cache。如果两者之一丢失,那么当下次构建时需要访问网络。
从 Rust 1.11.0 开始,Cargo 可以使用一个新的标志 --frozen
,其断言 Cargo 不能访问网络。一旦传递了这个标志,当 Cargo 试图访问网络时会立刻报错退出。
错误信息中包含需要访问网络的原因以帮助排查错误。注意,这个标志 不会改变 Cargo 的行为 ,其仅仅是在之前的命令已经准备好相应的资源后,声明 Cargo 接下来不应该访问网络。
在 Rust 1.36.0 中加入了 --offline
标志。这个标志告诉 Cargo 不要访问网络,同时尽可能用缓存的数据完成执行(如果可能的话)。
你可以用 cargo fetch
在断网之前下载好需要的依赖,然后通过 --offline
(或者 cargo设置选项)) 将这些依赖用在另一个项目中。
在 源替换 获取更多信息。
为什么 Cargo 重新构建了我的代码?
Cargo 负责增量编译你项目中的 crate。这意味着如果你连续进行两次 cargo build
,第二次运行不应该重新构建你的 crates.io 依赖。然而某些时候会发生 bug 导致 Cargo 重新构建你的代码。
我们很长时间内都想给这个问题提供更好的诊断信息,但是还没有取得进展。与此同时,你至少可以通过设置 CARGO_LOG
来对重新构建(rebuild)的原因进行一些诊断。
$ CARGO_LOG=cargo::core::compiler::fingerprint=info cargo build
这会使得 Cargo 打印出一大堆关于诊断和重新构建的信息,里面经常会有一点线索,但是大部分时候需要你费点力气去分析,因为这些信息暂时还不是那么容易阅读。
注意 CARGO_LOG
需要设置在你认为不应该但是却导致了重新构建的命令上。不幸的是 Cargo 目前还不支持事后分析——"为什么会发生重新构建?"
曾经的一些issue告诉我们以下情况会导致 crate 被重新构建:
-
一个构建脚本打印了
cargo:rerun-if-changed=foo
,但是foo
这个文件并不存在而且不会被生成。这导致 Cargo 一直执行构建脚本以生成这个文件,但是始终无法生成。这种情况的解决办法就是停止打印rerun-if-changed
。 -
连续的两次 Cargo build 可能会在某个依赖上启用不同的特性。例如第一个构建命令构建整个工作空间,第二个命令仅仅构建一个 crate,这可能会导致某个依赖使用了不同的特性,导致这个依赖和依赖它的内容被重新构建。很遗憾这没有完美的解决办法,如果可能的话,最好使得,不管你在工作空间中构建什么时,都让一个 crate 的特性保持不变。
-
一些文件系统在时间戳(timestamp)上显示出不寻常的行为。Cargo 主要利用文件的时间戳来决定重新构建是否应该发生,但是如果你用的是一个非标准的文件系统,其可能会影响到时间戳 (例如截断或偏离)。在这种情况下,可以开一个 issue ,我们会试着看看能否以某种方法适配这个文件系统。
-
一个并发的构建进程要么在删除构建制品,要么在修改文件。有时你有一个后台进程尝试构建或者检查项目。这个后台进程可能令人惊讶的删除了某些构建制品或者改变了文件,这会导致奇怪的重新构建。最好的解决方法是调整后台进程,避免与你的工作发生冲突。
如果在尝试 debug 你的问题后,还是无法解决,请开一个issue。