入门 The Cargo Team 2026-09-13 15:48:21 · 0 阅读

第14章 清单(Manifest)格式

每个包的 Cargo.toml 文件称为它的 manifest(清单),采用 TOML 格式编写,包含编译该包所需的元数据。关于 Cargo 如何查找 manifest 文件的更多细节,请参阅 cargo locate-project 一节。

每个 manifest 文件由以下几个部分组成:

[package]

Cargo.toml 的第一个节是 [package]

[package]
name = "hello_world" # 包的名称
version = "0.1.0"    # 当前版本,遵循 semver

Cargo 仅要求提供 name 字段。如果发布到注册表,注册表可能需要额外的字段。详见下文说明及发布章节中关于发布到crates.io 的要求。

name 字段

包名是用于引用该包的标识符。它在该包作为其他包的依赖项列出时使用,并作为推断的 lib 和 bin 目标(targets)的默认名称。

名称只能使用字母数字字符或 -_,且不能为空。

注意,cargo newcargo init 对包名施加了额外限制,例如强制其为有效的 Rust 标识符且非关键字。crates.io 则有更严格的限制,例如:

  • 仅允许使用 ASCII 字符。
  • 请勿使用保留名称。
  • 请勿使用特殊的 Windows 名称,如 “nul”。
  • 长度最多为 64 个字符。

version 字段

version 字段遵循SemVer 规范格式化:

版本必须包含三个数字部分:主版本号、次版本号及修订号(patch version)。

预发布版本号可以用连字符附加在后面,例如 1.0.0-alpha。预发布部分可以用点号分隔成多个组件:数字组件按数值比较,其余按字典序比较。例如 1.0.0-alpha.11 高于 1.0.0-alpha.4

构建元数据可以用加号附加在后面,例如 1.0.0+21AF26D3。这部分仅用于提供信息,Cargo 通常会忽略它。

Cargo 内置了语义化版本(Semantic Versioning)的概念:只要两个版本最左侧的非零 major/minor/patch 组件相同,就认为它们互相兼容。关于 Cargo 如何利用版本号解析依赖,详见Resolver一章。

该字段是可选的,默认为 0.0.0,但发布包时必须填写。

MSRV:1.75 之前此字段为必填项。

authors 字段

警告:此字段已弃用。

可选的 authors 字段用数组列出包的“作者”,可以是个人或组织。每个作者条目末尾可以用尖括号附上邮箱地址。

[package]
# ...
authors = ["Graydon Hoare", "Fnu Lnu <no-reply@rust-lang.org>"]

出于向后兼容的考虑,这个字段会出现在包的元数据中,也会通过 build.rs 里的 CARGO_PKG_AUTHORS 环境变量暴露出来。

edition 字段

edition 是可选字段,用于指定包编译时所使用的 Rust Edition。在 [package] 中设置 edition 会影响包内的所有 target/crate,包括测试套件、基准测试、二进制程序、示例等。

[package]
# ...
edition = '2024'
大多数 manifest 的 edition 字段会由 cargo new 自动填充为最新的稳定版本。目前默认情况下,cargo new 创建 manifest 时使用的 edition 为 2024。 如果 Cargo.toml 中缺少 edition 字段,为了向后兼容,系统会假定使用 2015 edition。请注意,通过 cargo new 创建的所有 manifest 都不会使用这种历史回退机制,因为它们会显式将 edition 指定为更新的值。

rust-version 字段

rust-version 字段用于告知 cargo 您的包支持哪个版本的 Rust 工具链。更多详情请参阅Rust 版本章节

description 字段

description 是对包的一段简短介绍。crates.io 会在您的包页面上展示此内容。该字段应为纯文本(而非 Markdown)。
[package]
# ...
description = "A short description of my package"

注意crates.io 要求必须设置 description

documentation 字段

documentation 字段指定了托管 crate 文档的网站 URL。如果 manifest 文件中未指定 URL,crates.io 会在文档构建完成并可用时(参见 docs.rs 队列),自动将您的 crate 链接到对应的 docs.rs 页面。
[package]
# ...
documentation = "https://docs.rs/bitflags"

readme 字段

readme 字段应指向包根目录下(相对于此 Cargo.toml)的一个文件路径,该文件包含关于包的一般信息。当您发布时,此文件会被传输到注册表。crates.io 会将其解析为 Markdown 并在 crate 页面上渲染。
[package]
# ...
readme = "README.md"

如果未指定该字段的值,且包根目录中存在名为 README.mdREADME.txtREADME 的文件,则默认使用该文件名。你可以通过将此字段设为 false 来禁止这种默认行为。如果将此字段设为 true,则假定默认值为 README.md

homepage 字段

homepage 字段应提供该包主页的 URL。

[package]
# ...
homepage = "https://serde.rs"

仅当 crate 拥有独立于源代码仓库和 API 文档的专门网站时,才应设置 homepage 的值。不要将其设置得与 documentationrepository 的值重复。

repository 字段

repository 字段应提供该包源代码仓库的 URL。

[package]
# ...
repository = "https://github.com/rust-lang/cargo"

licenselicense-file 字段

license 字段包含包所采用的软件许可证名称。license-file 字段包含许可证文本文件的路径(相对于当前 Cargo.toml 文件)。

crates.iolicense 字段解析为 SPDX 2.3 许可证表达式。名称必须是 SPDX 许可证列表 3.20 中已知的许可证。更多信息请参阅 SPDX 网站

SPDX 许可证表达式支持使用 AND 和 OR 运算符组合多个许可证。1

[package]
# ...
license = "MIT OR Apache-2.0"

使用 OR 表示用户可以任选其中一种许可证;使用 AND 表示用户必须同时遵守所有这些许可证;WITH 运算符表示带特殊例外的许可证。举几个例子:

  • MIT OR Apache-2.0
  • LGPL-2.1-only AND MIT AND BSD-2-Clause
  • GPL-2.0-or-later WITH Bison-exception-2.2

如果包使用的是非标准许可证,可以指定 license-file 字段来代替 license 字段。

[package]
# ...
license-file = "LICENSE.txt"

注意crates.io 要求必须设置 licenselicense-file 字段。

The keywords field

keywords 字段是一个描述本包的字符串数组,有助于在 registry 中搜索该包。你可以选择任何有助于别人找到这个 crate 的关键词。

[package]
# ...
keywords = ["gamedev", "graphics"]

注意crates.io 最多允许 5 个关键词。每个关键词必须是 ASCII 文本,最长 20 个字符,以字母或数字开头,且只能包含字母、数字、_-+

The categories field

categories 字段是一个字符串数组,表示本包所属的分类。

categories = ["command-line-utilities", "development-tools::cargo-plugins"]

注意crates.io 最多允许 5 个分类。每个分类必须与 https://crates.io/category_slugs 中列出的字符串完全一致。

The workspace field

workspace 字段用于配置当前包所属的 workspace。若未指定,系统会向上查找文件系统,将第一个包含 [workspace] 字段的 Cargo.toml 推断为根目录。当成员包不在 workspace 根目录的子目录下时,显式设置此字段很有必要。

[package]
# ...
workspace = "path/to/workspace/root"

如果 manifest 中已经定义了 [workspace] 表,则无法指定此字段。也就是说,一个 crate 不能既作为某个 workspace 的根 crate(包含 [workspace]),同时又作为另一个 workspace 的成员 crate(包含 package.workspace)。

更多信息,请参阅 workspaces 章节

build 字段

build 字段指定包根目录下的一个文件,该文件是用于构建原生代码的 build script。更多信息请查阅 build script 指南

[package]
# ...
build = "build.rs"

默认值为 "build.rs",即从包根目录加载名为 build.rs 的脚本。可以使用 build = "custom_build_name.rs" 指定其他文件路径,或者使用 build = false 禁用对 build script 的自动检测。

links 字段指定正在链接的原生库的名称。更多信息请查阅 build script 指南中的 links 部分。

例如,某个 crate 链接了名为“git2”的原生库(例如 Linux 上的 libgit2.a),可以指定:

[package]
# ...
links = "git2"

excludeinclude 字段

excludeinclude 字段可用于明确指定在打包项目以便发布时包含哪些文件,以及某些类型的变更跟踪(见下文描述)。exclude 字段中指定的模式标识出一组不纳入的文件,而 include 中的模式则指定明确纳入的文件。

[package]
# ...
exclude = ["/ci", "images/", ".*"]
[package]
# ...
include = ["/src", "COPYRIGHT", "/examples", "!/examples/big_example"]

注意: 运行 cargo package --list 以查看包中最终包含哪些文件。

如果两个字段都未指定,默认行为是包含包根目录下的所有文件,除非它们属于以下排除项。

如果未指定 include,则以下文件将被排除:

  • 如果包不在 git 仓库中,所有以点号开头的“隐藏”文件将被跳过。
  • 如果包位于 git 仓库中,任何被仓库 gitignore 规则或全局 git 配置忽略的文件都将被跳过。

如果指定了 include,则仓库的 gitignore 规则和全局 git 配置将不生效。

无论是否指定了 excludeinclude,以下文件始终被排除:

  • 所有子包都会被跳过(即包含 Cargo.toml 文件的子目录)。
  • 包根目录下名为 target 的目录将被跳过。

以下文件始终包含在内:

  • 包自身的 Cargo.toml 文件始终包含在内,无需在 include 中列出。
  • 一个最小化的 Cargo.lock 会自动包含。详见 cargo package
  • 如果指定了 license-file,它将被始终包含。

这些选项互斥;设置 include 会覆盖 exclude。如果需要在 include 匹配的文件集中排除某些文件,可以使用下面介绍的 ! 运算符。

模式采用 gitignore 风格,简要说明如下:

  • foo 匹配包内任意位置名为 foo 的文件或目录,等价于模式 **/foo
  • /foo 只匹配包根目录下名为 foo 的文件或目录。
  • foo/ 匹配包内任意位置名为 foo目录
  • 支持常见的 glob 通配符 *?[]
    • * 匹配零个或多个字符(不含 /)。例如 *.html 匹配包内任意位置以 .html 为扩展名的文件或目录。
    • ? 匹配任意单个字符(不含 /)。例如 foo? 匹配 food,但不匹配 foo
    • [] 匹配一段字符范围。例如 [ab] 匹配 ab[a-z] 匹配字母 a 到 z。
  • **/ 前缀匹配任意目录。例如 **/foo/bar 匹配任意位置下直接位于 foo 目录中的文件或目录 bar
  • /** 后缀匹配其中的所有内容。例如 foo/** 匹配 foo 目录内的所有文件,包括其子目录中的全部文件。
  • /**/ 匹配零个或多个目录。例如 a/**/b 匹配 a/ba/x/ba/x/y/b 等。
  • ! 前缀表示取反。例如同时写 src/*.rs!foo.rs,会匹配 src 目录下所有 .rs 文件,但排除名为 foo.rs 的文件。

在部分场景下,include/exclude 列表也用于变更跟踪。对于使用 rustdoc 构建的目标,该列表用于确定需要跟踪的文件,以判断目标是否应被重建。如果包包含一个 构建脚本,且该脚本未发出任何 rerun-if-* 指令,则当这些文件发生任何变更时,include/exclude 列表将用于跟踪构建脚本是否应重新运行。

The publish field

可用 publish 字段控制包可以发布到哪些注册表:

[package]
# ...
publish = ["some-registry-name"]

为防止包被误发布到注册表(如 crates.io),例如在公司内部保持包的私有性,您可以省略 version 字段。如果您希望更明确地禁用发布,可以设置:

[package]
# ...
publish = false

若 publish 数组中仅包含一个注册表,在未指定 --registry 标志时,cargo publish 命令将使用该注册表。

The metadata table

默认情况下,Cargo 会警告 Cargo.toml 中未使用的键,以帮助检测拼写错误等。不过,Cargo 会完全忽略 package.metadata 表,且不会发出警告。此部分可供工具使用,以便在 Cargo.toml 中存储包配置。例如:

[package]
name = "..."
# ...

# Metadata used when generating an Android APK, for example.
[package.metadata.android]
package-name = "my-awesome-android-app"
assets = "path/to/static"

您需要查阅工具的文档以了解如何使用此字段。对于使用 package.metadata 表的 Rust 项目,请参阅:

工作区级别也有类似的表格,位于 workspace.metadata。虽然 Cargo 没有规定这两个表格中内容的具体格式,但建议外部工具以一致的方式使用它们。例如,如果某个工具需要这样做是合理的,当 package.metadata 中缺少数据时,可以引用 workspace.metadata 中的数据。

default-run 字段

Manifest 的 [package] 部分中的 default-run 字段可用于指定由 cargo run 选择的默认二进制文件。例如,当同时存在 src/bin/a.rssrc/bin/b.rs 时:

[package]
default-run = "a"

[lints] 部分

通过在一个表格中为新级别分配默认 lint 级别,可以覆盖不同工具默认的 lint 级别,例如:

[lints.rust]
unsafe_code = "forbid"

这等价于:

[lints.rust]
unsafe_code = { level = "forbid", priority = 0 }

level 对应 rustc 中的lint 级别

  • forbid
  • deny
  • warn
  • allow

priority 是一个带符号整数,用于控制哪些 lint 或 lint 组覆盖其他 lint 组:

  • 数值较低(特别是负数)的优先级较低,会被较高的数值覆盖,并且会在传给 rustc 等工具的行上位于较前位置。

要确定某个特定的 lint 属于 [lints] 下的哪个表格,查看 lint 名称中 :: 之前的部分即可。如果没有 ::,则工具默认为 rust。例如,关于 unsafe_code 的警告归属于 lints.rust.unsafe_code,而关于 clippy::enum_glob_use 的 lint 则归属于 lints.clippy.enum_glob_use

示例如下:

[lints.rust]
unsafe_code = "forbid"

[lints.clippy]
enum_glob_use = "deny"

通常,这些配置只影响当前包的本地开发。Cargo 只会将它们应用于当前包,而不会应用于依赖项。至于依赖当前包的其他包,Cargo 会通过 --cap-lints 等特性来屏蔽来自非路径依赖的 lint。

MSRV:自 1.74 起生效

[hints] 部分

[hints] 部分用于为编译当前包指定提示(hint)。默认情况下,Cargo 在编译该包时会遵循这些提示,但顶层构建的包可以通过 [profile] 机制覆盖这些值。按设计,Cargo 忽略任何提示都是安全的:如果 Cargo 遇到不理解的提示,或者理解提示但不理解其取值,它只会发出警告而不会报错。因此,在 crate 中指定提示不会影响该 crate 的 MSRV。

某些提示可能关联一个不稳定特性开关(unstable feature gate),需要启用它才能应用相应配置。但如果不启用该特性开关,同样只会收到警告而不是错误。

目前尚无稳定的提示。关于一个不稳定提示的信息,请参阅 hint-mostly-unused 文档

MSRV:自 1.90 起生效。

[badges] 部分

[badges] 部分用于指定状态徽章,包发布后可以在 registry 网站上展示。

注意:crates.io 曾在网站上显示 crate 旁边的徽章,但该功能已被移除。包应将徽章放在 README 文件中,README 会展示在 crates.io 上(参见 readme 字段)。

[badges]
# `maintenance` 表用于指示 crate 的维护状态。注册表可能会使用此字段,但 crates.io 目前并不使用。
# 更多详情请参阅 https://github.com/rust-lang/crates.io/issues/2437
# 和 https://github.com/rust-lang/crates.io/issues/2438。
#
# `status` 字段为必填项。可选值如下:
# - `actively-developed`:正在添加新功能并修复 bug。
# - `passively-maintained`:没有新功能计划,但维护者打算响应提交的 issue。
# - `as-is`:功能已完善,维护者不再计划继续开发或提供支持,但该 crate 仍可正常实现其设计用途。
# - `experimental`:作者希望与社区共享,但无意满足任何特定使用场景。
# - `looking-for-maintainer`:当前维护者希望将 crate 移交给他人。
# - `deprecated`:维护者不建议使用此 crate(crate 的描述中可以说明原因,可能存在更好的替代方案,或 crate 存在作者不愿修复的问题)。
# - `none`:crates.io 上不显示徽章,因为维护者未选择指定其意向,潜在用户需自行调查。
maintenance = { status = "..." }

依赖部分

有关 [dependencies][dev-dependencies][build-dependencies] 以及特定目标的 [target.*.dependencies] 部分的信息,请参阅指定依赖页面

[profile.*] 部分

[profile] 表提供定制编译器设置的方法,例如优化和调试设置。更多细节请参阅配置章节


  1. 此前可以使用 / 分隔多个许可证,但此用法已弃用。

Cargo 目标

Cargo 包由若干目标(targets)组成,每个目标对应一个可编译成 crate 的源文件。包可以包含二进制可执行文件示例测试基准测试目标。这些目标列表可以在 Cargo.toml 清单文件中配置,通常由源文件的目录结构被自动推断

有关配置目标设置的详细信息,请参阅下文配置目标

库目标定义一个可供其他库和可执行文件使用及链接的“库”。库文件名默认为 src/lib.rs,库名称默认为包名,其中的短横线会被替换为下划线。一个包只能有一个库目标。库的设置可以在 Cargo.toml[lib] 表中进行自定义

# 在 Cargo.toml 中自定义库的示例。
[lib]
crate-type = ["cdylib"]
bench = false

二进制可执行文件

二进制目标是编译后可运行的可执行程序。二进制源文件可以是 src/main.rs,或者存放在src/bin/ 目录中。对于 src/main.rs,默认的二进制名称为包名。每个二进制的设置可以在 Cargo.toml[[bin]] 表中进行自定义

二进制文件可以使用包库的公共 API,并与 Cargo.toml[dependencies] 定义的依赖项进行链接。

可以使用带 --bin <bin-name> 选项的 cargo run 命令运行指定的二进制文件。cargo install 可以将可执行文件复制到公共目录。

# 在 Cargo.toml 中自定义二进制文件的示例。
[[bin]]
name = "cool-tool"
test = false
bench = false

[[bin]]
name = "frobnicator"
required-features = ["frobnicate"]

示例

位于 examples 目录下的文件是库所提供功能的示例用法。编译后,它们会被放到 target/debug/examples 目录中。

示例可以使用包的库的公共 API,也会链接 Cargo.toml 中定义的 [dependencies][dev-dependencies]

默认情况下,示例是可执行二进制文件(带 main() 函数)。你也可以指定 crate-type 字段,让示例编译为库:

[[example]]
name = "foo"
crate-type = ["staticlib"]

可以通过 cargo run 命令加上 --example <example-name> 选项来运行单个可执行示例;用 cargo build 加同样选项可以构建库类型的示例;用 cargo install 加同样选项则可以把可执行二进制复制到通用位置。示例默认会被 cargo test 编译,以防止代码腐化。如果示例中包含需要随 cargo test 一起运行的 #[test] 函数,可以把 test 字段设为 true

Tests

Cargo 项目中有两类测试:

  • 单元测试:库或二进制文件中(或任何通过test 字段启用的目标中)标记了 #[test] 属性的函数。这些测试可以访问所在目标内的私有 API。
  • 集成测试:独立的可执行二进制文件,同样包含 #[test] 函数,会与项目的库链接,并且只能访问其公共 API。

使用 cargo test 命令运行测试。默认情况下,Cargo 和 rustc 使用 libtest 框架,负责收集带有 #[test] 属性 的函数并并行执行它们,同时报告每个测试的成功或失败情况。如果想使用不同的框架或测试策略,请参见 harness 字段

注意:Cargo 中还有另一种特殊类型的测试:文档测试。它们由 rustdoc 处理,执行模型略有不同。更多信息请参阅 cargo test

集成测试

tests 目录 下的文件即为集成测试。运行 cargo test 时,Cargo 会将这些文件中的每一个编译为独立的 crate 并执行。

集成测试可以使用包库的公共 API。它们还会链接 Cargo.toml 中定义的 [dependencies][dev-dependencies]

若需在多个集成测试间共享代码,可将其放在单独的模块中,例如 tests/common/mod.rs,然后在每个测试文件中添加 mod common; 以引入该模块。

每个集成测试都会生成一个独立的可执行二进制文件,cargo test 会串行运行它们。在某些情况下,这种方式效率较低,因为编译耗时可能更长,且在运行测试时可能无法充分利用多核 CPU。如果集成测试数量较多,建议创建一个单一的集成测试,并将测试拆分为多个模块。libtest 框架会自动发现所有带有 #[test] 标记的函数并并行运行它们。向 cargo test 传递模块名,可以仅运行该模块内的测试。

如果存在集成测试,二进制目标会自动构建。这样集成测试就可以执行二进制文件,以验证和测试其行为。当集成测试构建和运行时,会设置 CARGO_BIN_EXE_<name> 环境变量,使其能够通过 envvar 函数定位可执行文件。

基准测试

基准测试使用 cargo bench 命令来测试代码性能。其结构与测试相同,每个基准测试函数都标记有 #[bench] 属性。与测试类似:

  • 基准测试位于benches 目录中。
  • 在库和二进制中定义的基准测试函数可以访问其所属目标中的私有 API。benches 目录中的基准测试可以使用公有 API。
  • 可以通过bench 字段定义默认进行基准测试的目标。
  • 可以通过harness 字段禁用内置的测试框架。

注意#[bench] 属性目前处于不稳定状态,仅在nightly 通道上可用。crates.io 上有一些包可以帮助在稳定通道上运行基准测试,例如 Criterion

配置目标

Cargo.toml 中的 [lib][[bin]][[example]][[test]][[bench]] 这些配置段支持类似的配置项,用于指定如何构建对应的目标。像 [[bin]] 这种双括号形式是 TOML 的表数组,也就是说你可以写多个 [[bin]] 段,让 crate 生成多个可执行文件。库只能有一个,所以 [lib] 是普通的 TOML 表。

下面是各目标的 TOML 配置总览,各字段的详细说明见后文。

[lib]
name = "foo"           # 目标名称。
path = "src/lib.rs"    # 目标的源码文件。
test = true            # 默认会被测试。
doctest = true         # 文档示例默认会被测试。
bench = true           # 默认会被基准测试。
doc = true             # 默认会生成文档。
proc-macro = false     # proc-macro 库需设为 `true`。
harness = true         # 使用 libtest 测试框架。
crate-type = ["lib"]   # 要生成的 crate 类型。
required-features = [] # 构建该目标所需的 feature(对 lib 不适用)。

name 字段

name 字段指定目标名称,对应生成产物的文件名。对库来说,它就是依赖方引用该库时所用的 crate 名称。

对库目标,默认取包名,并将其中的连字符替换为下划线。对默认的二进制目标(src/main.rs),默认同样取包名,但连字符保持不变。对于自动发现的目标,默认取目录名或文件名。

[lib] 外,其他目标都必须填写此字段。

path 字段

path 字段指定 crate 源码的位置,相对于 Cargo.toml 文件。

如未指定,则根据目标名称使用推断出的路径

test 字段

test 字段决定是否默认通过 cargo test 测试该目标。对于 lib、bins 和 tests,默认值为 true

注意:默认情况下,示例会通过 cargo test 构建以确保其可继续编译,但默认不会进行测试。若将示例的 test 设为 true,则也会将其作为测试构建并运行其中定义的任何 #[test] 函数。

doctest 字段

doctest 字段决定是否默认通过 cargo test 测试文档示例。此选项仅对库有效,对其他部分无影响。库的默认值为 true

bench 字段

bench 字段决定是否默认通过 cargo bench 对目标进行基准测试。对于 lib、bins 和 benchmarks,默认值为 true

doc 字段

doc 字段决定是否默认将目标包含在 cargo doc 生成的文档中。对于库和二进制文件,默认值为 true

注意:如果二进制文件名与 lib 目标名相同,则会被跳过。

plugin 字段

此选项已弃用且未使用。

proc-macro 字段

proc-macro 字段表示该库是一个过程宏参考)。此选项仅对 [lib] 目标有效。

harness 字段

harness 字段表示是否将 --test 参数传递给 rustc。启用后,编译器会自动引入 libtest 库,它是负责收集并运行带有 #[test] 属性的测试或带有 #[bench] 属性的基准测试的驱动程序。所有目标的默认值均为 true

如果设置为 false,则你需要自行定义 main() 函数来运行测试和基准测试。

无论 harness 是否启用,cfg(test) 条件表达式 始终处于激活状态。

crate-type 字段

crate-type 字段定义了目标生成的 crate 类型。它是一个字符串数组,允许为单个目标指定多种 crate 类型。此字段仅适用于库和示例;二进制文件、测试和基准测试始终使用 “bin” crate 类型。默认值如下:

目标Crate 类型
常规库"lib"
过程宏库"proc-macro"
示例"bin"

可用选项包括 binlibrlibdylibcdylibstaticlibproc-macro。你可以在 Rust 参考手册中了解更多关于不同 crate 类型的信息。

required-features 字段

required-features 字段指定了构建该目标所需启用哪些特性。如果任何必需的特性未被启用,该目标将被跳过。此字段仅适用于 [[bin]][[bench]][[test]][[example]] 部分,对 [lib] 无效。

[features]
# ...
postgres = []
sqlite = []
tools = []

[[bin]]
name = "my-pg-tool"
required-features = ["postgres", "tools"]

edition 字段

edition 字段定义该 target 使用的 Rust edition。如果不指定,默认使用 [package] 中的 edition 字段

注意:该字段已弃用,将在未来的 Edition 中移除。

Target 自动发现

默认情况下,Cargo 会根据文件系统上的文件布局自动确定要构建的 target。如果有些 target 不符合标准目录布局,可以通过 [lib][[bin]][[test]][[bench]][[example]] 等 target 配置表来添加。

也可以关闭自动发现,只构建手动配置的 target。在 [package] 段中把 autolibautobinsautoexamplesautotestsautobenches 设为 false,即可禁用对应类型 target 的自动发现。

[package]
# ...
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false

一般只有在特殊情况下才需要关闭自动发现。比如,你希望库里有一个名为 bin模块,这就会出问题,因为 Cargo 通常会把 bin 目录下的所有内容都当作可执行文件来编译。这种场景的目录结构示例如下:

├── Cargo.toml
└── src
    ├── lib.rs
    └── bin
        └── mod.rs

要避免 Cargo 把 src/bin/mod.rs 推断为可执行文件,可以在 Cargo.toml 中设置 autobins = false 来禁用自动发现:

[package]
# …
autobins = false

注意:对于 2015 版式的包,如果在 Cargo.toml 中手动定义了至少一个 target,自动发现(auto-discovery)的默认值为 false。从 2018 版式开始,默认值始终为 true

MSRV:自 1.27 版本起,autobinsautoexamplesautotestsautobenches 字段得到遵守。

MSRV:自 1.83 版本起,autolib 字段得到遵守。

Rust 版本

rust-version 是一个可选字段,用于告知 Cargo 你的包支持的 Rust 工具链版本。

[package]
# ...
rust-version = "1.56"

Rust 版本必须是一个至少包含一个组件的裸版本号,不能包含 semver 操作符或预发布标识符。在检查 Rust 版本时,编译器预发布标识符(如 -nightly)会被忽略。

MSRV:自 1.56 版本起得到遵守。

用途

诊断:

当在不受支持的工具链上编译你的包时,Cargo 会将其作为错误报告给用户。这明确了支持预期,并避免了报告诸如无效语法或标准库缺失功能等不直接相关的诊断信息。这会影响包中所有的 Cargo target,包括 binaries、examples、测试套件、benchmarks 等。用户可以通过 --ignore-rust-version 标志选择在不支持的工具链上构建该包。

开发辅助:

cargo add 会自动将依赖的版本要求选择为与你的 rust-version 兼容的最新版本。如果该版本不是最新版,cargo add 会通知用户,以便他们决定是保留该依赖还是更新你的 rust-version

resolver 在挑选依赖时可能会考虑 Rust 版本。

其他工具也可以利用此字段,例如 cargo clippyincompatible_msrv lint

注意: 可以通过 --ignore-rust-version 选项忽略 rust-version 的限制。

支持预期

以下是普遍预期,部分包可能会注明它们并不遵循这些规则。

完整(Complete):

所有功能,包括二进制文件和 API,在支持的 Rust 版本和各个 feature 下均可用。

已验证(Verified):

包的功能在其支持的 Rust 版本上经过验证,包括自动化测试。 另请参阅我们的 Rust 版本 CI 指南

可修补(Patchable):

在许可证允许的情况下, 用户可以使用你包的 fork 来覆盖本地依赖。 在此场景下,Cargo 可能会加载被修补依赖的整个 workspace,该 workspace 应在支持的 Rust 版本下正常工作,即使 workspace 中的其他包支持不同的 Rust 版本。

依赖支持(Dependency Support):

为了支持上述目标, 预期每个依赖的版本要求至少支持一个与你的 rust-version 兼容的版本。 但是, 不要求依赖规范排除与你的 rust-version 不兼容的版本。 事实上,支持这两种情况可以平衡支持较旧 Rust 版本的用户的需要与不支持旧版本的用户的需要。

设置和更新 Rust 版本

支持哪些 Rust 版本是以下因素之间的权衡:

  • 维护者因不使用 Rust 工具链或其依赖项的新特性而产生的成本
  • 用户因包使用工具链新特性(例如通过迁移到标准库特性而非 polyfill 来缩短构建时间)而受益的成本考量
  • 包对支持较旧 Rust 版本用户的可用性

注意: 更改 rust-version 被视为次要的不兼容性变更

建议:为自己的包明确一个支持哪些 Rust 版本、何时调整的策略,让用户可以拿它和自己的策略对照。如果不兼容,用户也能据此判断:错过通用改进、或遇到不会再修复的阻塞性 bug,是否可以接受。

最简单的策略是始终支持最新的 Rust 版本。

根据你的风险偏好,次简单的做法是继续维护支持旧 Rust 版本的旧主版本或次版本。

如何选择支持的 Rust 版本

你的包的用户在追踪自己支持的 Rust 版本时,通常会参考:

  • 其 Rust 工具链提供方的支持策略,比如 Rust Project 或某个 Linux 发行版
    • 注意:Rust Project 仅为最新版本提供 bug 修复和安全更新。
  • 一个固定的周期,定期用新工具链重新验证自己的包,比如每年第一个版本、每 5 个版本一次。

此外,用户一般不会在 Rust 新版本发布后立刻切换,而是需要时间知晓并重新验证,或者他们的时间表和你并不完全一致。

版本策略示例:

  • “N-2”,即“支持最新版本,并留出 2 个版本的更新宽限期”
  • 支持每个偶数版本,并留出 2 个版本的更新宽限期
  • 支持今年日历年度内的所有版本,并留出一年的更新宽限期

注意:要找出与当前项目兼容的最低 rust-version,可以使用 cargo-msrv 这类第三方工具。

更新时间表

当按你的策略不再需要支持某个 Rust 版本时,可以立即或按需更新 rust-version

如果让 rust-version 相对策略有所滞后,就等于给用户提供了更宽的升级宽限期。但这种滞后难以预测,不能指望它来对齐用户实际追踪的 Rust 版本。

rust-version 偏离既定策略越远,用户就越容易推断出一个你并未打算承诺的策略,一旦预期落空,只会让他们感到失望。

当允许一定程度的偏差时,就会产生一个问题:放弃对旧版本的支持到什么程度才算“合理”?不同的人可能得出截然不同的结论,围绕这一点的讨论往往让各方都感到挫败。对于那些希望避免此类冲突的人——尤其是新贡献者或偶尔参与的贡献者——这种氛围往往会让他们感到被削弱话语权。他们要么觉得自己没有立场提出这个问题,要么担心冲突会阻碍自己的更改被合并。

工作区内的多种策略

Cargo 允许在一个工作区内支持多种策略。

在特定的 Rust 版本下验证特定包可能会变得复杂。像 cargo-hack 这样的工具可以提供帮助。

对于跨策略共享的依赖项,必须使用最低的共同版本。因为 Cargo 会统一 SemVer 兼容的版本,这可能会限制工作区内使用较高 rust-version 的成员访问该共享依赖项的某些功能。

要允许用户修补工作区中某个成员依赖项,工作区中的每个包都必须在该工作区支持的最旧 Rust 版本中可加载。

当使用 incompatible-rust-versions = "fallback" 时,一个包的 Rust 版本可能会影响另一个具有不同 Rust 版本的包所选择的依赖项版本。更多细节请参阅resolver 章节。

一种或多种策略

减轻支持旧版 Rust 负面影响的办法之一,是将策略应用于你仍支持较旧的主要或次要版本。你很可能仍然需要制定策略,规定开发分支相较于那些主要或次要版本的发布分支所支持的 Rust 版本。

仅在“需要”时更新开发分支,有助于减少需要支持的发布分支数量。

另一个问题是什么可以回溯移植到这些发布分支。如果在次要版本之间回溯移植新功能,那么下一个可用版本就会缺失这些功能,这可能被视为破坏性变更,从而违反 SemVer。回溯移植更改还伴随着引入 bug 的风险。

支持旧版本需要付出代价。这一成本取决于包中 bug 的风险与影响,以及可接受的向后移植程度。按需创建发布分支并将向后移植的负担交给社区,是平衡这一成本的有效手段。

目前,依赖管理工具尚无法报告非最新版本仍处于支持状态,这迫使用户必须通过阅读文档来手动确认支持情况。

例如,Rust 的版本支持策略可以制定如下:

  • 开发分支追踪 Rust 项目最新的稳定发行版,并根据需要更新:
    • 修改 rust-version 时,需提升次要版本号
  • 项目支持本日历年度内的所有版本,并额外提供一年的宽限期:
    • 支持受支持 Rust 版本的最后一个次要版本将获得社区提供的 bug 修复
    • 修复措施必须向后移植到开发分支与目标受支持 Rust 版本之间的所有受支持次要发行版

评论 (0)