How to require/build dependencies for the specific feature enabled only?

I have following shared dependencies,
but not all of them are really wanted if only A or B is active:

[features]
# default = ["a"]
a = []
b = []

[dependencies]

# for A feature enabled only?
chrono = { version = "0.4.41", features = ["serde"] }
librqbit-core = "5.0.0"

# for B feature enabled only?
log = "0.4.28"
regex = "1.11.2"

How to skip B requirement if I'm using only A for this library?

[features]
# default = ["a"]
a = ["chrono", "librqbit-core"]
b = ["log", "regex"]

[dependencies]

# for A feature enabled only?
chrono = { version = "0.4.41", features = ["serde"], optional = true }
librqbit-core = { version = "5.0.0", optional = true }

# for B feature enabled only?
log = { version = "0.4.28", optional = true }
regex = { version = "1.11.2", optional = true }
1 Like

Thanks much - I forgot about the optional = true key in my attempts!

To prevent unwanted features, that should probably be

[features]
# default = ["a"]
a = ["dep:chrono", "dep:librqbit-core"]
b = ["dep:log", "dep:regex"]

[dependencies]

# for A feature enabled only?
chrono = { version = "0.4.41", features = ["serde"], optional = true }
librqbit-core = { version = "5.0.0", optional = true }

# for B feature enabled only?
log = { version = "0.4.28", optional = true }
regex = { version = "1.11.2", optional = true }

note the dep:s

2 Likes

Not sure I understand, do you mean the dep: prefix unlike solution above?

Yes, see Features - The Cargo Book

1 Like

Thanks, added dep: prefix even it looks like working without that..

Did you read the link provided? It's very possible that without "dep:<dependency_name>" you get behavior you didn't intend even if it holds up to a very mild amount of scrutiny.

Any optional dependency—doesn't matter if it's a normal dependency, build dependency, target-agnostic dependency, or target-specific dependency—always creates an implied feature of the same name unless at least one feature references that dependency directly via "dep:<dependency_name>". Do not attempt to apply some additional logic. It really is that simple.

For example, cargo will error when the below Cargo.toml is used:

[dependencies]
foo = { version = "0.0.0", optional = true }
[features]
foo = []

for the same reason it will fail for the below:

[features]
foo = []
foo = []

Namely that TOML, and likely cargo if TOML didn't, forbids duplicate keys. Because the first example didn't define at least one feature whose array contained "dep:foo", a feature is automatically added that looks like foo = ["dep:foo"]; however since a feature already exists with that name, cargo will fail.

The policy that I adhere to is to always disable implied features even if I end up defining something that is logically equivalent. In other words the below are exactly the same:

[dependencies]
foo = { version = "0.0.0", optional = true}
[dependencies]
foo = { version = "0.0.0", optional = true }
[features]
foo = ["dep:foo"]

I always choose the latter for consistency, to not accidentally expose a feature I didn't want, and to explicitly advertise the defined features. To hammer home how features are defined, here is another example:

[dependencies]
foo = "0.0.0"
buzz = "0.0.0"

[target.'cfg(any(unix, not(unix))'.dependencies]
buzz = { version = "0.0.0", optional = true }

[target.'cfg(all(unix, not(unix))'.build-dependencies]
foo = { version = "0.0.0", optional = true }

[target.jibberish.dependencies]
bar = { version = "0.0.0", optional = true }

[target.'cfg(windows)'.dependencies]
fizz = { version = "0.0.0", optional = true }

Using the above "algorithm" I defined, what features exist? Simple, we see that there exists at least one optional dependency named foo, bar, fizz, and buzz. We see that there is no feature under [features] that suppresses the implied features those define; thus we get below:

[features]
foo = ["dep:foo"]
bar = ["dep:bar"]
fizz = ["dep:fizz"]
buzz = ["dep:buzz"]

It doesn't matter that in reality foo is never optional seeing how the defined build dependency only applies to targets that are both unix and not(unix) which of course is impossible. It doesn't matter that the dependency fizz only exists for Windows. It doesn't matter that we have a contradiction that buzz is both optional and not optional (since all platforms are either unix or not(unix)). It doesn't matter that bar is defined for a platform that doesn't, and likely never will, exist.

This also means you shouldn't simply rely on features when dealing with target-specific logic. Thus based on the above, you should use #[cfg(all(windows, feature = "fizz"))] in your code and not simply #[cfg(feature = "fizz")] since the feature fizz is defined for not(windows) as well.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.