Cargo 的功能开关用于表达条件编译和可选依赖。包在 Cargo.toml 的 [features] 表中定义一组命名功能,每项功能都可以启用或关闭。正在构建的包可通过 --features 等命令行选项启用功能;依赖项的功能则可以在 Cargo.toml 的依赖声明中启用。
注意: crates.io 目前把新发布的 crate 或版本限制为最多 300 项功能。特殊情况会逐案批准例外,详见相关公告。也欢迎通过 crates.io 的 Zulip 频道参与解决方案讨论。
功能开关示例一章还展示了更多用法。
[features] 配置节
功能定义在 Cargo.toml 的 [features] 表中。每项功能对应一个数组,列出它要启用的其他功能或可选依赖。以下以二维图像处理库为例,说明如何按需包含对不同图片格式的支持:
[features]
# Defines a feature named `webp` that does not enable any other features.
webp = []
定义此功能后,可以使用 cfg 表达式,在编译时按条件包含支持该功能的代码。例如,可以在包的 lib.rs 中写入:
#![allow(unused)]
fn main() {
// This conditionally includes a module which implements WEBP support.
#[cfg(feature = "webp")]
pub mod webp;
}
Cargo 通过 rustc 的 --cfg 选项设置包的功能。代码可以用 cfg 属性或 cfg 宏检查它们是否启用。
一项功能可以列出其他需要启用的功能。例如,ICO 格式可以包含 BMP 和 PNG 图像,因此启用 ICO 支持时,也应确保启用了这两种格式:
[features]
bmp = []
png = []
ico = ["bmp", "png"]
webp = []
功能名称可以使用 Unicode XID 标准中的字符,该集合包含大多数文字。名称还可以以 _ 或数字 0 至 9 开头;首字符之后也可以出现 -、+ 或 .。
注意: crates.io 对功能名称有额外限制,只允许 ASCII 字母或数字,以及 _、-、+。
default 功能
除非明确启用,否则各项功能默认都不启用。可以通过定义 default 功能改变这一行为:
[features]
default = ["ico", "webp"]
bmp = []
png = []
ico = ["bmp", "png"]
webp = []
构建包时会启用 default,继而启用其列出的功能。以下两种方式可以改变这一行为:
注意: 应谨慎选择默认功能集合。默认功能方便常见用法,让用户不必逐项选择,但也有代价。除非指定 default-features = false,依赖声明会自动启用默认功能。因此,特别是同一依赖在依赖图中出现多次时,很难保证默认功能始终关闭。所有依赖它的包都必须指定 default-features = false,才能避免这些功能被启用。
另一个问题是:从默认集合中移除功能可能构成 SemVer 不兼容变更。所以,在把功能加入默认集合前,应确认将来会保留它。
可选依赖
依赖可以标记为可选,表示默认不编译它。例如,假设二维图像处理库使用一个外部包处理 GIF 图片,可以这样声明:
[dependencies]
gif = { version = "0.11.1", optional = true }
默认情况下,这个可选依赖会隐式定义如下功能:
[features]
gif = ["dep:gif"]
因此,只有启用 gif 功能时才包含这个依赖。代码可以使用同样的 cfg(feature = "gif") 条件,也可以像普通功能一样用 --features gif 启用它,详见命令行功能选项。
有时不希望对外暴露与可选依赖同名的功能。例如,该依赖只是内部实现细节,或想把多个可选依赖分组,又或者希望采用更合适的名称。在 [features] 表中任意位置用 dep: 前缀引用这个可选依赖,就会关闭它的同名隐式功能。
注意: dep: 语法从 Rust 1.60 开始可用。更早的版本只能使用隐式功能名称。
例如,为支持 AVIF 图像格式,库需要同时启用另外两个依赖:
[dependencies]
ravif = { version = "0.6.3", optional = true }
rgb = { version = "0.8.25", optional = true }
[features]
avif = ["dep:ravif", "dep:rgb"]
在这个例子中,avif 功能会启用列出的两个依赖。由于这些依赖是 crate 的内部细节,不希望用户分别启用,所以用 dep: 避免了隐式生成 ravif 和 rgb 功能。
注意: 平台专用依赖也可以按条件包含依赖。它们根据目标平台决定是否包含,而不是根据功能开关决定。
依赖项的功能
可以在依赖声明中启用依赖自身的功能。features 键列出需要启用的功能:
[dependencies]
# Enables the `derive` feature of serde.
serde = { version = "1.0.118", features = ["derive"] }
可以用 default-features = false 关闭依赖的默认功能:
[dependencies]
flate2 = { version = "1.0.3", default-features = false, features = ["zlib-rs"] }
注意: 这不一定能保证默认功能关闭。如果其他依赖声明也使用 flate2,但没有指定 default-features = false,默认功能仍会启用,详见后面的功能合并。
也可以在 [features] 表中启用依赖的功能,语法是 "package-name/feature-name"。例如:
[dependencies]
jpeg-decoder = { version = "0.1.20", default-features = false }
[features]
# Enables parallel processing support by enabling the "rayon" feature of jpeg-decoder.
parallel = ["jpeg-decoder/rayon"]
如果 package-name 是可选依赖,"package-name/feature-name" 还会同时启用这个依赖本身,这往往并非预期行为。可以加入问号,写成 "package-name?/feature-name";这样只有其他配置已经启用该可选依赖时,才会启用指定功能。
注意: ? 语法同样从 Rust 1.60 开始可用。
例如,库增加了序列化支持,需要为某些可选依赖启用相应功能,可以这样配置:
[dependencies]
serde = { version = "1.0.133", optional = true }
rgb = { version = "0.8.25", optional = true }
[features]
serde = ["dep:serde", "rgb?/serde"]
这个例子中,启用 serde 功能会启用 serde 依赖;同时,如果其他配置已启用 rgb 依赖,就为它启用 serde 功能。
命令行功能选项
以下命令行选项控制启用哪些功能:
-
--features FEATURES:启用列出的功能。多个功能可以用逗号或空格分隔。通过 shell 运行 Cargo 时,如果使用空格,要把全部功能名称放在引号内,例如--features "foo bar"。构建工作区中的多个包时,可以用package-name/feature-name指定某个成员的功能。 -
--all-features:启用命令行所选全部包的所有功能。 -
--no-default-features:不启用所选包的default功能。
注意: 具体行为请查看相应子命令的文档,并非所有子命令都支持全部选项。
功能合并
功能属于定义它的包。在一个包上启用某项功能,不会自动启用其他包中的同名功能。
同一个依赖被多个包使用时,Cargo 会把这些包为该依赖启用的全部功能取并集,再用这个集合构建依赖。这有助于只使用一份该依赖,详见解析器文档的功能一节。
例如,winapi 提供了大量功能。如果包依赖 foo,而 foo 为 winapi 启用 fileapi 和 handleapi;另一个依赖 bar 启用 std 和 winnt,那么构建 winapi 时就会同时启用这四项功能。
图:同一依赖的功能会取并集,保留原文官方结构图。
因此,功能应该具有可叠加性:启用一项功能不应关闭其他能力,通常任意功能组合都应安全。功能也不应引入 SemVer 不兼容变更。
例如,若要按需支持 no_std 环境,不要定义一个 no_std 功能,而应定义一个用于启用 std 的 std 功能:
#![allow(unused)]
#![no_std]
fn main() {
#[cfg(feature = "std")]
extern crate std;
#[cfg(feature = "std")]
pub fn function_that_requires_std() {
// ...
}
}
互斥功能
少数功能可能彼此不兼容。应尽量避免这种设计,因为需要协调依赖图中所有对该包的使用,防止它们同时启用互斥功能。如果确实无法避免,可以加入编译错误检测,例如:
#[cfg(all(feature = "foo", feature = "bar"))]
compile_error!("feature \"foo\" and feature \"bar\" cannot be enabled at the same time");
除了使用互斥功能,还可以考虑:
-
把不同功能拆成独立的包。
-
调整代码结构,让功能可以同时启用,再通过运行时选项决定实际行为。例如,使用配置文件、命令行参数或环境变量选择行为。
检查解析后的功能
复杂依赖图中,很难看出各包的功能是如何启用的。cargo tree 提供多种选项,用于检查和展示启用的功能。可以尝试:
-
cargo tree -e features:在依赖图中展示功能,并显示是哪个包启用了每项功能。 -
cargo tree -f "{p} {f}":更紧凑地显示每个包启用的功能,功能名称用逗号分隔。 -
cargo tree -e features -i foo:反转依赖树,展示功能如何流入指定包foo。完整依赖图可能过于庞大;当需要解释某个包启用了哪些功能、为什么启用时,可以使用此选项。cargo tree文档底部的示例说明了如何阅读结果。
功能解析器版本 2
可以通过 Cargo.toml 的 resolver 字段指定不同的功能解析器:
[package]
name = "my-package"
version = "1.0.0"
resolver = "2"
如何指定解析器版本,详见解析器版本。
版本 "2" 会在一些不希望合并功能的情形中避免合并。完整说明见解析器一章,主要包括:
某些场景必须避免合并。例如,构建依赖启用了 std,而同一个包又作为普通依赖用于 no_std 环境时,把 std 功能合并进来会导致构建失败。
代价是可能延长构建时间:同一个依赖会以不同功能集合分别构建多次。使用版本 "2" 的解析器时,建议检查重复构建的依赖,以缩短总构建时间。
如果不必用不同功能分别构建这些重复包,可以在依赖声明的 features 列表中补充功能,使其最终功能集合一致,Cargo 就只需构建一次。用 cargo tree --duplicates 可以发现重复依赖;特别留意同一版本的重复条目。检查解析后的功能提供了更多信息。
通过 --target 进行交叉编译时,不必针对构建依赖做这项调整,因为这种情况下,构建依赖本来就始终与普通依赖分开构建。
解析器版本 2 的命令行选项
resolver = "2" 也改变了 --features 和 --no-default-features 命令行选项的行为。
使用版本 "1" 时,只能启用当前工作目录所在包的功能。例如,工作区有 foo 和 bar 两个包;在 foo 的目录里执行 cargo build -p bar --features bar-feat 会失败,因为此时 --features 只能为 foo 启用功能。
使用 resolver = "2" 时,可以为通过 -p 或 --workspace 选中的任意包启用功能。例如:
# This command is allowed with resolver = "2", regardless of which directory
# you are in.
cargo build -p foo -p bar --features foo-feat,bar-feat
# This explicit equivalent works with any resolver version:
cargo build -p foo -p bar --features foo/foo-feat,bar/bar-feat
此外,resolver = "1" 时,--no-default-features 只关闭当前目录所在包的默认功能;版本 "2" 则会关闭全部工作区成员的默认功能。
构建脚本
构建脚本可以检查 CARGO_FEATURE_<name> 环境变量,判断包启用了哪些功能。<name> 是转换为大写、并把 - 替换为 _ 后的功能名称。
目标要求的功能
required-features 字段可以在指定功能未启用时,禁用相应的 Cargo 构建目标。细节请查阅链接中的说明。
SemVer 兼容性
启用功能不应引入 SemVer 不兼容变更,例如不应以破坏现有用法的方式修改现有 API。SemVer 兼容性一章详细说明了哪些变更保持兼容。
新增或移除功能定义、可选依赖时需要谨慎,因为某些操作可能破坏向后兼容性,详见兼容性章节的 Cargo 部分。一般遵循以下原则:
通常可以在次版本更新中安全执行:
通常不应在次版本更新中执行:
具体例外与示例请参阅上述链接。
功能文档与发现方式
建议记录包提供了哪些功能,例如在 lib.rs 顶部添加文档注释。可以参考 regex crate 的源码,其渲染结果可在 docs.rs 查看。若有用户指南等其他文档,也可以在那里说明,例如 serde.rs。对于可执行程序项目,可以把功能写入 README 或其他项目文档,例如 sccache。
清楚的文档有助于说明哪些功能仍不稳定或不建议使用。例如,某个可选依赖只是实现细节,不希望用户把它单独列为功能,就不应把它加入对外公布的功能列表。
发布在 docs.rs 的文档可以通过 Cargo.toml 元数据,控制构建文档时启用的功能,详见 docs.rs 元数据说明。
注意: Rustdoc 对标注 API 所需功能提供实验性支持,具体见 doc_cfg 文档。例如 syn 的文档使用彩色标注框,说明各 API 需要哪些功能。
发现功能
在库的 API 文档中说明功能,能让用户更容易发现有哪些功能以及它们的用途。如果文档不易找到,可以查看 Cargo.toml。有时源码也难以定位;crates.io 的 crate 页面在提供信息的情况下会链接到源码仓库。也可以使用 cargo vendor 或 cargo-clone-crate 下载源码并检查。
功能组合
功能开关属于条件编译。若要实现 100% 覆盖,需要测试的配置组合数量呈指数增长。测试、文档构建以及 Clippy 等工具默认只使用默认功能集合。
应根据项目需要考虑不同功能组合的测试策略与工具。各项目在时间、资源和覆盖某些情形的成本收益之间有不同取舍。常见配置包括启用或关闭默认功能、若干特定组合,或所有功能组合。
来源与许可
原文:Features。作者或贡献者:Rust 项目与 Cargo 文档贡献者。本中文版本为翻译;代码、注释和原文示例输出保留原样,必要的技术澄清已在相应段落说明。适用许可:MIT(Cargo采用MIT或Apache-2.0双许可,本稿选用MIT)。
许可原文
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.











暂无评论内容