Rust中的条件编译

 

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/