掌握 Cargo.toml
的格式规则,避免挫败感
在 JavaScript 和其他语言中,我们称令人惊讶或不一致的行为为“Wat!”(即“什么!?”)。
例如,在 JavaScript 中,空数组加空数组会产生一个空字符串,[] + [] === ""
。Wat!
在另一个极端,某种语言有时会表现出令人惊讶的一致性。我称之为“Wat Not”。
Rust 通常比 JavaScript 更加一致。然而, 一些与 Rust 相关的格式会带来惊喜。
具体来说,本文将介绍 Cargo.toml
中的九个 wats 和 wat nots。
回想一下,Cargo.toml
是定义 Rust 项目配置和依赖项的清单文件。
其格式 TOML(Tom's Obvious, Minimal Language)表示嵌套的键/值对和/或数组。
JSON 和 YAML 是类似的格式。与 JSON 不同的是,TOML 被设计为易于人类阅读和编写。
这九个 wats 和 wat nots 的旅程不会像 JavaScript 的怪癖那样有趣(谢天谢地)。
然而,如果你曾经对 Cargo.toml
的格式感到困惑,我希望本文能让你感觉更好。
最重要的是,当你了解了这九个 wats 和 wat nots 后,希望你能更轻松有效地编写 Cargo.toml
。
本文不是关于“修复” Cargo.toml
。该文件格式在其主要用途上非常出色:指定 Rust 项目的配置和依赖项。
相反,本文旨在理解其格式及其怪癖。
Wat 1:依赖项 vs. 配置文件部分名称
你可能知道如何在 Cargo.toml
中添加 [dependencies]
部分。这样的部分指定了发布依赖项,例如:
[dependencies]
serde = "1.0"
同样,你可以使用 [dev-dependencies]
部分指定开发依赖项,使用 [build-dependencies]
部分指定构建依赖项。
你可能还需要设置编译器选项,例如优化级别和是否包含调试信息。你可以通过发布、开发和构建的配置文件部分来设置这些选项。
你能猜出这三个部分的名称吗?是 [profile]
、[dev-profile]
和 [build-profile]
吗?
不!它们是 [profile.release]
、[profile.dev]
和 [profile.build]
。Wat!
[dev-profile]
会比 [profile.dev]
更好吗?[dependencies.dev]
会比 [dev-dependencies]
更好吗?
我个人更喜欢带点的名称。(在“Wat Not 9”中,我们将看到点的强大之处。)然而,我愿意记住依赖项和配置文件的工作方式不同。
Wat 2:依赖项继承
你可能会认为点适用于配置文件,而连字符更适用于依赖项,因为 [dev-dependencies]
继承自 [dependencies]
。换句话说,[dependencies]
中的依赖项在 [dev-dependencies]
中也可用。那么,这是否意味着 [build-dependencies]
也继承自 [dependencies]
?
不![build-dependencies]
不继承自 [dependencies]
。Wat!
我发现这种 Cargo.toml
的行为既方便又令人困惑。
Wat 3:默认键
你可能知道,可以这样写:
[dependencies]
serde = { version = "1.0" }
也可以这样写:
[dependencies]
serde = "1.0"
这里的原则是什么?一般的 TOML 中如何指定一个键为默认键?
你不能!一般的 TOML 没有默认键。Wat!
Cargo TOML 对 [dependencies]
部分中的 version
键进行了特殊处理。这是 Cargo 特有的功能,而不是一般的 TOML 功能。据我所知,Cargo TOML 没有其他默认键。
Wat 4:子功能
使用 Cargo.toml
的 [features]
,你可以创建依赖项不同的项目版本。这些依赖项本身的功能也可能不同,我们称之为子功能。
在这里,我们创建了两个项目版本。默认版本依赖于带有默认功能的 getrandom
。wasm
版本依赖于带有 js 子功能的 getrandom
:
[features]
default = []
wasm = ["getrandom-js"]
[dependencies]
rand = { version = "0.8" }
getrandom = { version = "0.2", optional = true }
[dependencies.getrandom-js]
package = "getrandom"
version = "0.2"
optional = true
features = ["js"]
在这个例子中,wasm 是我们项目的一个功能,依赖于依赖项别名 getrandom-rs
,它代表带有 js 子功能的 getrandom
crate 版本。
那么,如何在避免冗长的 [dependencies.getrandom-js]
部分的情况下给出相同的规范?
在 [features]
中,将 getrandom-js
替换为 "getrandom/js"
。我们可以这样写:
[features]
default = []
wasm = ["getrandom/js"]
[dependencies]
rand = { version = "0.8" }
getrandom = { version = "0.2", optional = true }
一般来说,在 Cargo.toml
中,功能规范(如 wasm = ["getrandom/js"]
)可以列出:
- 其他功能
- 依赖项别名
- 依赖项
- 一个或多个依赖项“斜杠”一个子功能
这不是标准的 TOML,而是 Cargo.toml
特有的简写。
附加:你如何用简写表示你的 wasm
功能应包括带有两个子功能的 getrandom
:js 和 test-in-browser
?
答案:列出依赖项两次。
wasm = ["getrandom/js","getrandom/test-in-browser"]
Wat 5:目标的依赖项
我们已经看到如何指定发布、调试和构建的依赖项。
[dependencies]
#...
[dev-dependencies]
#...
[build-dependencies]
#...
我们已经看到如何指定各种功能的依赖项:
[features]
default = []
wasm = ["getrandom/js"]
你会怎么猜测我们如何为各种目标(例如某个版本的 Linux、Windows 等)指定依赖项?
我们在 [dependencies]
前加上 target.TARGET_EXPRESSION
前缀,例如:
[target.x86_64-pc-windows-msvc.dependencies]
winapi = { version = "0.3.9", features = ["winuser"] }
按照一般 TOML 的规则,我们也可以这样说:
[target]
x86_64-pc-windows-msvc.dependencies={winapi = { version = "0.3.9", features = ["winuser"] }}
我觉得这种前缀语法很奇怪,但我无法提出更好的替代方案。不过,我确实想知道为什么功能不能以相同的方式处理:
# 不允许
[feature.wasm.dependencies]
getrandom = { version = "0.2", features=["js"]}
Wat Not 6:目标 cfg 表达式
这是我们的第一个“Wat Not”,即它是一种让我感到惊讶的一致性。
除了具体目标(如 x86_64-pc-windows-msvc
),你还可以在单引号中使用 cfg
表达式。例如:
[target.'cfg(all(windows, target_arch = "x86_64"))'.dependencies]
我不认为这是一个“wat!”。我认为这很棒。
回想一下,cfg
是“配置”的缩写,是 Rust 通常用于条件编译代码的机制。例如,在我们的 main.rs
中,我们可以这样说:
if cfg!(target_os = "linux") {
println!("This is Linux!");
}
在 Cargo.toml
中,在目标表达式中,几乎支持整个 cfg
迷你语言。
all()
、any()
、not()
target_arch
target_feature
target_os
target_family
target_env
target_abi
target_endian
target_pointer_width
target_vendor
target_has_atomic
unix
windows
cfg
迷你语言唯一不支持的部分(我认为)是你不能使用 --cfg
命令行参数设置值。此外,一些 cfg
值(如 test
)没有意义。