Should we make our own semantic versioning spec?

Pretty regularly I see discussions pop up about version 1.0.0 that makes it evident that there are 2 ways in which the rust community feels different about the meaning of version numbers than what the semver spec says:

  1. In the official spec, version 0.x.y and 0.x.z are considered not compatible. But cargo (and most rust programmers) assumes that version 0.x.y and 0.x.z (with z > y) are compatible, and that breaking changes must increase x.

  2. The spec says that version 0.x.y is for initial development only and is not considered stable. The spec's FAQ says that if it's used in production or depended on by others, it should be at version >1.0.0 already. But despite our past efforts to push crates to version 1.0.0, we still have many stable, production-quality crates depended on by many people that are at version 0.x.y (15 of the 20 most-downloaded crates on crates.io are version 0.x.y).

I think this mismatch will continue to spark discussions forever. And the time we spend arguing about this could be better spent on more productive things.

That's why I think it could be a good idea to make our own version of semver to clarify what the official stance of the rust community is on the meaning of version numbers. We have a choice here between being:

  • A) Descriptive: Try to describe the current situation as accurately as possible. The benefit of this would be that when someone (like your boss) complains about one of your dependencies being version 0.x.y, you have a document to point to to say "no, that's fine, that's just how we do things in Rust".

  • B) Prescriptive: Write a spec that doesn't fully match how people currently use version numbers. But this should then come paired with a concerted effort to get people to use the version numbers in the way that the new spec dictates. If we say, for example, that production-quality crates should be at version 1.0.0 or above, we should actively go to production-quality crates that are at version 0.x.y to ask them to change the version number.

If we make such a spec, the github RFC about it will presumably get a million comments, so I don't want to be the one who writes it. But I still think it would be a good idea to have a rust-semver spec.

I also posted the same question on the rust subreddit but I'd like to get comments from this forum too, as I feel like there are many people on reddit who don't visit this forum and vice-versa.

1 Like

I think codifying what has become the de-facto standard (as far as I can tell) in Rust would make the most sense at this point.

1 Like

There was some discussion a while back about making the next version of the semver spec. It hasn’t gone forward yet, but if it does, I’ll be involved. (I maintain the semver crate.)

Cargo already does what the most popular implementations of semver actually do. It’s such a minor difference, and is actually compatible with the spec, so I think that deviating would be more harmful, not less.

3 Likes

@gbutler69: I agree!

@steveklabnik: I replied here (maybe posting to both sites was not such a good idea after all). But in short: it's not just about the technicality of compatibility between 0.x.y, and 0.x.z, but also about when to move from 0.x to 1.0. There is a big discrepancy there between what semver says and what library authors do.

It's cool :slight_smile:

Another interesting statistic: from the 100 most-downloaded crates on crates.io (if I counted correctly),

  • 0.x.y: 72 crates
  • 1.x.y: 23 crates
  • 2.x.y: 4 crates
  • 3.x.y: 1 crate
  • 4.x.y+: none

On the other hand, at major version 0, where the second number is effectively the major version:

  • 0.1.x: 18 crates
  • 0.2.x: 11 crates
  • 0.3.x: 10 crates
  • 0.4.x: 9 crates
  • 0.5.x to 0.9.x: 15 crates
  • 0.10.x to 0.15.x: 7 crates
  • beyond that: 3 crates. The winner is syntex_syntax at version 0.59.1.

Library authors are certainly very hesitant to change that leading version number, not just from 0 to 1 but also beyond that.

I also want to draw attention to 2 other (highly Rust-specific) situations where potentially breaking changes are currently often introduced in patch/minor versions:

  • Backwards-incompatible soundness fixes
  • Breaking type inference when adding impls

Backwards-incompatible soundness fixes

It's possible to have APIs that seem reasonable but that for some subtle reason can't actually be sound. I'm thinking of the issue with std::thread::scoped , a recently-discoved issue in stdweb, and how MutexGuard was accidentally Sync. In all of these cases, the unsoundness can't be fixed without breaking backwards compatibility.

Do these backwards-incompatible soundness fixes require a major version bump? In the case of MutexGuard, the decision that was made was no, because it breaks very little code, and the code that it breaks was probably unsound to begin with (and so should break). But it was nevertheless a breaking change, so that was technically a violation of the semver spec.

A Rust-specific semver could specify that soundness fixes MAY be introduced without bumping the major version number, but only if they break very little code and/or only if the code they break was suspicious to begin with.

Breaking type inference when adding impls

My first PR that I submitted to rustc was one that added a few trait impls to the numeric primitive types (and references to them). All it did was add some impls, but it still made some code stop compiling. The breakage was all due to type inference: in the code that broke, some variable or temporary had no explicit type annotation, but when there was only 1 impl it could only have 1 possible value. But after adding the impl, suddenly more than 1 value was possible, and the code didn't compile anymore. The PR was, after a lot of discussion, eventually accepted, and published in a minor version release.

Here too, the right decision was technically a violation of semver. But a Rust semver spec could specify that adding new impls of traits MAY be done in a minor/patch release even if it breaks type inference in dependent code (because otherwise, you'd have to increase the major version in almost every release).

By the way, in this blog post (in the section "What are the stability caveats?"), the Rust team wrote that it will sometimes do breaking changes for precisely these 2 situations that I just described:

We reserve the right to fix compiler bugs, patch safety holes, and change type inference in ways that may occasionally require new type annotations.

Surely it's a good idea to put a bit more of a spotlight on this fact, and on the fact that not just rust itself but also libraries may sometimes fix soundness holes or add impls of traits in minor/patch versions even if they are technically breaking changes.

6 Likes

But it was nevertheless a breaking change, so that was technically a violation of the semver spec.

The semver spec does not define what “breaking” means, and leaves it up to the project. We have a policy that soundness fixes are not considered breaking for version purposes, as they’re semantically closer to bug fixes.

3 Likes

Is that policy written down somewhere? Could you link to it? Because I wasn't aware of it, and I don't think I'm the only one. I bet if you asked a bunch of regular rust programmers (i.e. not core team members) about whether they think backwards-incompatible soundness fixes require a major version bump, you won't get a unanimous "no".

1 Like

https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md

1 Like

I’m on my phone so I can’t link but RFCs 1105 and 1122.

1 Like

Right, that settles that. So the thing I'm arguing that we should create already exists, and I just didn't know about it (despite actually having searched for it, but clearly not enough). Well this is embarrassing...

3 Likes

I'm fine with the first being used only if there are breaking changes, which could mean a lot of crates staying at 0.x.y. It reminds me of some discussions around the Kafka version number. Which was used a lot in production before the 1.0 release. Dropping support for Java 6,7 they are at 2.0 now.
I think it would be awesome if we could make the version number automatic. Something in the line if comparing the public part to the previous one, no changes last on is upped, only additions the second one, any breaking change (however small) bump the first one.

You may be interested in rust-semverver.

2 Likes

Looks exactly what I meant. But the real power would be if it was used for every publication. Having breaking code after updating versions, while not changing the major one, is something every? language struggles with.

I think Rust libraries largely use semver as intended, with the understanding that humans usually don't like restrictions or responsibility and therefore, making libraries 1.0 or above is unlikely unless there is a clear benefit to the author, as well as, the observation that people will accept less reliable solutions in production if it means spending less and having a quicker time to market. I.e. nothing surprising or interesting going on here.

3 Likes

Personally, I think way too much is being made of the whole semver/breaking-change debate. To me, if I choose to use a library, I'm interested if it meets my needs now. I understand implicitly that there are no guarantees of future support. To think otherwise (unless you have a paid contract with an SLA, and even then, it's not guaranteed) is simply not a realistic position. You can hope that future versions will be backwards compatible. You can hope that the current compatible version will have bug fixes and security fixes for as long as you need to depend on the library without you having to do anything, but, it is my assertion, that that is all a gift and not a gift that should be taken for granted or assumed to be able to be relied upon. If you want long-term support for tools, libraries, languages, etc. that you want to choose you have a couple of options:

  • Hope for the best and continually debate about semver and breaking change policies
  • Help maintain the dependencies you are interested in
  • Pay somebody and have a contract to maintain the dependencies you are interested in

I can pretty much guarantee that bullet point #1 will not get you very far except by the altruism and charity of those focused on the 2nd and 3rd bullet points.

(edited at suggestion)

8 Likes

Most crates do semver properly - even though it doesn't match the original semver spec it still isn't something causing issues in the community. Instead of this, we can focus on a "breaking changes spec" so that we can enlist different breaking changes which will in turn help crates to have backward compatible versions.

1 Like