diff --git a/contents/cargo/reference/features.md b/contents/cargo/reference/features.md index 15a346f2..66b880b4 100644 --- a/contents/cargo/reference/features.md +++ b/contents/cargo/reference/features.md @@ -124,7 +124,7 @@ parallel = ["jpeg-decoder/rayon"] ## feature同一化 `feature` 只有在定义的包中才是唯一的,不同包之间的 `feature` 允许同名。因此,在一个包上启用 `feature` 不会导致另一个包的同名 `feature` 被误启用。 -当一个依赖被多个包所使用时,这些包对该依赖所设置的 `feature` 将被进行合并,这样才能确保该依赖只有一个拷贝存在,大家可以查看[这里](https://doc.rust-lang.org/stable/cargo/reference/resolver.html#features)了解下解析器如何对 feature 进行解析处理。 +**当一个依赖被多个包所使用时,这些包对该依赖所设置的 `feature` 将被进行合并,这样才能确保该依赖只有一个拷贝存在,这个过程就被称之为同一化**。大家可以查看[这里](https://doc.rust-lang.org/stable/cargo/reference/resolver.html#features)了解下解析器如何对 feature 进行解析处理。 这里,我们使用 `winapi` 为例来说明这个过程。首先,`winapi` 使用了大量的 `features`;然后我们有两个包 `foo` 和 `bar` 分别使用了它的两个 features,那么在合并后,最终 `winapi` 将同时启四个 features : @@ -149,3 +149,76 @@ pub fn function_that_requires_std() { // ... } ``` + +#### 彼此互斥的feature +某极少数情况下,features 之间可能会互相不兼容。我们应该避免这种设计,因为如果一旦这么设计了,那你可能需要修改依赖图的很多地方才能避免两个不兼容 feature 的同时启用。 + +如果实在没有办法,可以考虑增加一个编译错误来让报错更清晰: +```toml +#[cfg(all(feature = "foo", feature = "bar"))] +compile_error!("feature \"foo\" and feature \"bar\" cannot be enabled at the same time"); +``` + +当同时启用 `foo` 和 `bar` 时,编译器就会爆出一个更清晰的错误:feature `foo` 和 `bar` 无法同时启用。 + +总之,我们还是应该在设计上避免这种情况的发生,例如: + +- 将某个功能分割到多个包中 +- 当冲突时,设置 feature 优先级,[cfg-if](https://crates.io/crates/cfg-if) 包可以帮助我们写出更复杂的 `cfg` 表达式 + +#### 检视已解析的features +在复杂的依赖图中,如果想要了解不同的 features 是如何被多个包多启用的,这是相当困难的。好在 `cargo tree` 命令提供了几个选项可以帮组我们更好的检视哪些 features 被启用了: + +`cargo tree -e features` ,该命令以依赖图的方式来展示已启用的 features,包含了每个依赖包所启用的特性: +```shell +$ cargo tree -e features +test_cargo v0.1.0 (/Users/sunfei/development/rust/demos/test_cargo) +└── uuid feature "default" + ├── uuid v0.8.2 + └── uuid feature "std" + └── uuid v0.8.2 +``` + +`cargo tree -f "{p} {f}"` 命令会提供一个更加紧凑的视图: +```shell +% cargo tree -f "{p} {f}" +test_cargo v0.1.0 (/Users/sunfei/development/rust/demos/test_cargo) +└── uuid v0.8.2 default,std +``` + +`cargo tree -e features -i foo`,该命令会显示 `features` 会如何"流入"指定的包 `foo` 中: +```shell +cargo tree -e features -i uuid +uuid v0.8.2 +├── uuid feature "default" +│ └── test_cargo v0.1.0 (/Users/sunfei/development/rust/demos/test_cargo) +│ └── test_cargo feature "default" (command-line) +└── uuid feature "std" + └── uuid feature "default" (*) +``` + +该命令在依赖图较为复杂时非常有用,使用它可以让你了解某个依赖包上开启了哪些 `features` 以及其中的原因。 + +大家可以查看官方的 `cargo tree` [文档](https://doc.rust-lang.org/stable/cargo/commands/cargo-tree.html)获取更加详细的使用信息。 + +## Feature解析器V2版本 +我们还能通过以下配置指定使用 V2 版本的解析器( [resolver](https://doc.rust-lang.org/stable/cargo/reference/resolver.html#resolver-versions) ): +```toml +[package] +name = "my-package" +version = "1.0.0" +resolver = "2" +``` + +V2 版本的解析器可以在某些情况下避免 feature 同一化的发生,具体的情况在[这里](https://doc.rust-lang.org/stable/cargo/reference/resolver.html#feature-resolver-version-2)有描述,下面做下简单的总结: + +- 为特定平台开启的 `features` 且此时并没有被构建,会被忽略 +- `Build-dependencies` 和 `proc-macros` 不再跟普通的依赖共享 `features` +- `Dev-dependencies` 的 `features` 不会被启用,除非正在构建的对象需要它们(例如测试对象、示例对象等) + +对于部分场景而言,feature 同一化确实是需要避免的,例如,一个构建依赖开启了 `std` feature,而同一个依赖又被用于 `no_std` 环境,很明显,开启 `std` 将导致错误的发生。 + +说完优点,我们再来看看 V2 的缺点,其中增加编译构建时间就是其中之一,原因是同一个依赖会被构建多次(每个都拥有不同的 feature 列表)。 + +> 由于此部分内容可能只有极少数的用户需要,因此我们并没有对其进行扩展,如果大家希望了解更多关于 V2 的内容,可以查看[官方文档](https://doc.rust-lang.org/stable/cargo/reference/features.html#feature-resolver-version-2) +