What's best practice for using optional feature sub-dependencies vs. a direct dependency?

⚓ Rust    📅 2025-07-10    👤 surdeus    👁️ 3      

surdeus

I'm using a package called menu which provides a library crate with an optional noline feature that depends on a package of that same name. Or rather, it looks like the menu package declares an optional dependency on that package:

[dependencies]
noline = { version = "0.5.0", optional = true }

…but does not list it as an explicit feature. If I'm understanding https://doc.rust-lang.org/cargo/reference/features.html#optional-dependencies correctly optional dependencies implicitly become package features (at least in lieu of any further explicit configuration).

With that background, my question is whether in the Rust community it's considered best practice to simply rely on this dependency-feature for my own code's usage. That is, in order to use this noline feature of menu then my own crate will use noline::builder::EditorBuilder in its own source code.

My general practice at least in other languages is that any dependencies I import myself get listed as top-level dependencies even if they "happen to" already be hanging around as sub-dependencies of something. But now in this case it seems like it could be argued either way:

  • no, leave it out. it is redundant to directly depend on noline because I'm already explicitly asking for a compatible version by using the noline dependency-feature on my own menu dependency. it doesn't just "happen to" be installed — I opted into an optional (sub-)dependency and so my code is good to use it too
    • but… could a future version of menu choose to implement its noline feature in a different way that leaves me without the actual noline dependency? potentially I am snooping into internal implementation details that the menu = { version = "0.6.1", features = ["noline"] } feature is one that comes from an optional dependency that I want to also depend on.
  • yes, depend on noline directly myself alongside the menu crate with that feature enabled — for basically the reasons in the sub-bullet above, that how a package implements a feature is not really "public API" and I shouldn't rely on it
    • but now, don't I risk getting or later updating to a different version of the noline shared-ish dependency, and so my code will be passing in an object from noline == 0.5.1 or noline == 0.6.42 when maybe the menu crate still expects noline == 0.5.0

What would be idiomatic Rust/Cargo usage here? Should I include in my own package just:

[dependencies]
menu = { version = "0.6.1", features = ["noline"] }

or should I explicitly:

[dependencies]
menu = { version = "0.6.1", features = ["noline"] }
noline = "0.5.1"

Or perhaps put another way, would it be considered a semver-major change for menu to change what packages its own noline feature makes available to my own code — or is that an implementation detail I shouldn't be relying on?

8 posts - 3 participants

Read full topic

🏷️ rust_feed