I found two sources of potential API instability that could be solved with similar approach.
Case 1: added trait method causes breakage
// in crate A
trait Foo {
fn do_stuff(&self);
}
// in crate B
trait Bar {
fn do_something_else(&self);
}
// in crate C, which depends on A and B
use a::Foo;
use b::Bar;
x.do_stuff();
x.do_something_else();
Now if either A adds default fn Foo::do_something_else
or B adds fn Bar::do_stuff
C breaks due to ambiguity. This is considered minor and can be resolved using UFCS however the breakage is still annoying and UFCS is ugly.
This is even worse for core
where e.g. TryFrom
/TryInto
could not have been added to prelude in old editions because people wrote their own traits to work on stable. Now people have to jump whole edition to have it in prelude. Since core
is used by everything the likelihood of breakage is high.
Case 2: functionality got moved to a feature
Crate A has a default feature foo
which is undesirable in crate B, so crate B specifies default-features = false }
Crate A wants to make some dependency optional but this dependency was previously used by B. Making the dependency optional but default doesn't work, so the only solution is for A to issue breaking release.
It seems this is most often experienced when crates make std
optional but there are other cases too.
Additional annoyance
I work with the newest compiler to have better error messages etc but want to support older MSRV. After I write the code I test with older MSRV and find out I have to rewrite it because I misremembered which features are available in the given MSRV.
This is unrelated but happens to be helped with by the solution!
Solution
The solution for both would be to make the new crate versions somehow act as if they were older.
For case 1
Add attribute #[since = "$version"]
to newly added items in the dependency (A, B in the example). Then if a consumer crate (C in the example) specifies older version of dependency in its Cargo.toml
those items become invisible. Attempt to use them will cause compilation error with helpful message suggesting to bump the version. In case of std
&co stable
attribute is reused and combined with rust-version
field in Cargo.toml
. This behavior is only enabled with option hide-unrequested-features = true
(feel free to bikeshed) to avoid breaking existing things.
This helps with the annoyance above at least when it comes to libs API.
For case 2
Add optional default-before
field to features:
[features]
foo = { default-before = "1.4", deps = ["bar"] }
If a crate depends on version 1.3 it will see foo
feature as always on even when default-features
is false
. To truly remove it the crate has to depend on 1.4
or higher.
I imagine this could also reduce many discussions around "this is minor change but let's do crater run; oh breaks x crates, is it too many?" and enable faster library development.
Can you see any way this could be improved even more? Would it be worth adding?