1

In Rust Cargo, how can I include optional features of dependencies, depending on the inclusion of another dependency?

Specifically, I would like to offer a "serde" feature, which enables serde support. What I can do is [1]

[dependencies]
serde = { version = "1.0", optional = true }

Rust exports this dependency as a feature automatically (as I understand it). However, I use other dependencies, which offer a "serde" feature, too:

[dependencies]
otherpackage = { version = "1.0", features = ["serde"] }
serde = { version = "1.0", optional = true }

Now, the serde feature of "otherpackage" should only be included if the "serde" feature is activated for this package. But cargo does not let me do

[features]
serde = [ "serde", "otherpackage/serde" ]

[dependencies]
otherpackage = { version = "1.0" }
serde = { version = "1.0", optional = true }

I understand this is due to a name clash between the "serde" feature and the "serde" dependency, which I think is also exported as a feature. So, in essence there are now two "serde" features, which obviously does not work.

How can I solve this with still offering a "serde" feature and without renaming the "serde" package? Ideally, I would just like to disable the automatic exporting of the serde dependency as a feature or be able to specify a feature dependency of the "serde" package.

kmdreko
  • 42,554
  • 6
  • 57
  • 106

1 Answers1

2

How can I solve this with still offering a "serde" feature and without renaming the "serde" package?

Since Rust 1.60, “namespaced features” allow this, as documented in the Cargo documentation on optional dependencies. When referring to a crate from within your [features] table, prefix it with dep:, like this:

[features]
serde = [ "dep:serde", "otherpackage/serde" ]

[dependencies]
otherpackage = { version = "1.0" }
serde = { version = "1.0", optional = true }

As soon as you use dep: for an optional dependency, it is no longer treated as implicitly creating a feature, so this also allows giving an entirely different name to the feature than to any dependencies it enables.


The following solution is obsolete and included for historical reference.

Prior to namespaced features, this had to be done by renaming the “serde” dependency. However, this doesn't need to disrupt anything else; as described in “Advanced Cargo [features] Usage”, you can rename it and rename it back. Set up your Cargo.toml like this:

[features]
serde = [ "serde_cr", "otherpackage/serde" ]

[dependencies]
otherpackage = { version = "1.0" }
serde_cr = { package = "serde", version = "1.0", optional = true }

This will make the crate name serde_cr visible to your code. Then rename it back using an explicit extern crate in your lib.rs:

extern crate serde_cr as serde;

However, this has the disadvantage of causing your package to have a feature named serde_cr.

Kevin Reid
  • 37,492
  • 13
  • 80
  • 108
  • Thanks. I did not know it is possible to rename a crate back in lib.rs. However, it seems that this solution requires an ugly hack when using `derive(Deserialize)`, because rustc complains that it `can't find crate for "serde"`. Using `#[serde(crate = "serde")]` below each `derive(Deserialize)` solves the issue: [how to reexport Serialize, Deserialize?](https://github.com/serde-rs/serde/issues/1465#issuecomment-800686252) – LiterallyCode Aug 30 '21 at 11:44
  • Namespaced features has been merged and stabilized if I'm reading correctly. Should this answer be updated? – kmdreko Sep 22 '22 at 00:20
  • @kmdreko Done. I kept the old version since it explains what one might see in old libraries. – Kevin Reid Sep 22 '22 at 00:33
  • @KevinReid wow! quick work! – kmdreko Sep 22 '22 at 00:34