Rust 的条件编译
在 Cargo.toml 中使用 [features]
来加入条件编译 feature。如下定义了一个 rand 的 feature:
[package]
name = "example"
version = "0.1.0"
[features]
rand = []
然后在代码中可以使用该 feature
#[cfg(feature = "rand")]
pub mod rand;
如果作为 crate,则其他人可以像下面一样来启用依赖的feature:
[dependencies]
example = { version = "0.1.0", features = ["rand"]}
还可以开启多个 feature,并且可以有依赖关系:
[features]
foo = []
bar = []
foobar = ["foo", "bar"] # 开启了 foobar 则自动开启了 foo 和 bar
onlydepfoo = ["foo"] # 开启 onlydepfoo 则自动开启了 foo
onlydepbar = ["bar"] # 开启 onlydepbar 则自动开启了 bar
在 Cargo.toml 中定义的 feature 会被 Cargo 通过命令行参数 –cfg 传给 rustc,最终由后者完成编译
Feature 的名称要求:
- Feature 名称可以包含来自 Unicode XID standard 定义的字母,允许使用 _ 或 0-9 的数字作为起始字符,在起始字符后,还可以使用 -、+ 或 . 。
- crate.io 要求名称只能由 ASCII 字母数字、_、- 或 + 组成。
默认 feature: 默认情况下,所有的 feature 都会被自动禁用,可以通过 default 来启用它们:
default = ["foo"] # 默认启用 foo
foo = []
bar = []
foobar = ["foo", "bar"] # 开启了 foobar 则自动开启了 foo 和 bar
可选依赖: 当依赖被标记为 “可选 optional” 时,意味着它默认不会被编译。
[dependencies]
somecrate = {version = "1", optional = true}
这种可选依赖的写法会自动定义一个与依赖同名的 feature,也就是 somecrate feature,这样一来,当我们启用 somecrate
feature 时,该依赖库也会被自动引入并启,可以通过 --features somecrate
的方式启用 feature ,也就启用了该依赖库
[feature] 中定义的 feature 还不能与已引入的依赖库同名,但在 nightly 中已提供支持了
也可以通过 显式定义 feature 的方式来启用这些可选依赖库,
[dependencies]
crate1 = { version = "0.8", optional = true }
crate2 = { version = "1", optional = true }
[features]
crate1_and_crate2 = ["crate1", "crate2"]
依赖库自身的 feature
- 使用
features
启用依赖库的 feature - 使用
default-features
来禁止默认 feature[dependencies] serde = { version = "1", features = ["derive"] } flate2 = { version = "1", default-features = false, features = ["zlib"] }
注意:这种方式未必能成功禁用 default,原因是可能会有其它依赖也引入了 flate2,并且没有对 default 进行禁用,那此时 default 依然会被启用。
间接开启依赖库的 feature
[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/feature-name” 语法形式不仅会开启指定依赖的指定 feature,若该依赖是可选依赖,那还会自动将其引入
使用:
cargo run --featues feat1, feat2, ...
Feature 的同一化
feature 只有在定义的包中才是唯一的,不同包之间的 feature 允许同名。因此,在一个包上启用 feature 不会导致另一个包的同名 feature 被误启用。
当一个依赖被多个包所使用时,这些包对该依赖所设置的 feature 将被进行合并,这样才能确保该依赖只有一个拷贝存在,这个过程就被称之为同一化。大家可以查看这里了解下解析器如何对 feature 进行解析处理。
启用一个 feature 不应该导致某个功能被禁止,好的做法,默认最小集合,如果有需要,才在代码中启用feature
查看已启用的 features,该命令以依赖图的方式来展示已启用的 features,包含了每个依赖包所启用的特性:
cargo tree -e features
cargo tree -f "{p} {f}" # 结果更紧凑
cargo tree -e features -i foo # 该命令会显示 features 会如何"流入"指定的包 foo 中
# 该命令在依赖图较为复杂时非常有用,使用它可以让你了解某个依赖包上开启了哪些 features 以及其中的原因。
区分可选依赖项和功能名称
feature的命名可选依赖项时使用dep:前缀,如下
[dependencies]
# Optionally, process data in parallel.
rayon = { version = "1.10.0", optional = true }
# Optionally, use a faster hash table.
rustc-hash = { version = "2.1.0", optional = true }
[features]
# Allow selecting "max perf" mode with just one feature,
# instead of managing multiple optional dependencies.
max_performance = ["dep:rayon", "dep:rustc-hash"]
注意:通过 dep:packagename
会去掉 optional 带来的隐式行为:定义一个和crate同名的feature,如上所示,使用了 dep:rayon
,则不再有 rayon
这个 feature
cargo semver-checks 会找到上面这种情况下带来的问题
参考:
- https://course.rs/cargo/reference/features/intro.html
- https://predr.ag/blog/breakage-in-the-cargo-toml-how-rust-package-features-work/