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.
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.
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.)
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.)