Is disabling features of a dependency considered a breaking change?

I have a crate that accidentally depended on another crate with std enabled (via default features). A change was made to it to no longer do that (unless its own std feature is enabled). I'm now not certain how to bump the version number.

Strictly speaking it is possible that a downstream crate experiences breakage - if it also had the dependency via other means without std but accidentally depended on an std code. However:

  • This scenario assumes somewhat broken Cargo.toml which doesn't specify the feature
  • The scenario should be unlikely
  • The crate is not 1.0 yet
  • To my knowledge the crate is not very widely used
  • The fix is reasonably easy - just enable the feature
  • No code changed other than the feature being unset

So I'm strongly considering to just bump 0.3.0 -> 0.3.1 instead of 0.3.0 -> 0.4.0

Just double checking, do you see anything wrong with my reasoning? Is there some argument to release it as incompatible that I didn't consider?

Code is only allowed to depend on other code it explicitly declares as a dependency. If some code uses a feature of a crate without declaring that feature, because "a 3rd party will enable the feature anyways", then that code is broken. So no, you don't need to care about it, and this isn't a breaking change.

2 Likes

It's usually considered not breaking or at least, acceptable breakage. However, you definitely can break someone in this way. I have done so in the past. We haven't reverted the change when it broke someone for the reasons that @H2CO3 mentions.

3 Likes

A variant of this happened by virtue of me putting out a new regex-syntax 0.7 release a few weeks ago: build is failing in 1.8 because a Unicode feature isn't enabled, but previously worked in 1.7 · Issue #982 · rust-lang/regex · GitHub

It broke done folks because they were implicitly relying on some features of regex-syntax being enabled without enabling them explicitly. When 0.7 came out, it exposed this and broke them. There is apparently no way to mitigate this and is difficult to defend against. (See the issue for more details.)

4 Likes

Tangent:

Features are fundamentally fragile in this way, because they change what is seen by all dependents at the request of one, and there is no currently implemented way to validate that all necessary feature-dependencies are present.

Therefore, when designing a library, if something doesn't have to be a feature, favor not making it one. In particular, crates that re-export other crates conditionally on features are an unnecessary use of features; have your users depend on the individual crates directly, and completely avoid this fragility.

Necessary uses of features include all cases where a local trait is implemented for a dependency's type, which is often the case for libraries' std features. (I don't know that I want to say that all crates that can have a smaller no_std crate split out should have, though; that sounds quite awkward even though it is feasible.)

2 Likes

Thank you all for your responses! This is pretty much exactly what I thought and so I did make a non-breaking release.

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.