Minor Semver Issue
epage.github.io
epage.github.io
> I believe the author was intending to say that some crate authors are not adhering to 100% of the semver spec.
Article is being disingenuous here. The original author absolutely intended to call this a violation of the semver spec, with all of the connotations that brings. And frankly they're right: by "not adhering to" the spec, you break their expectations and break their builds.
> With this continuous release model, what value am I providing users by bumping minor on all API additions? Most API additions end up being small and drawing attention to them with a minor release creates just as much noise for the user to sift through as patch releases.
The rest of the article answers that: if you bump minor on all API additions, you allow others to specify a caret version with a minor version number, as the spec says they should be able to.
Either follow the damn spec, or make it very clear that you're not following the spec so that we know not to use your libraries. Certainly don't try to blame other people for being rightly annoyed that you've made more work for them by violating the spec for the sake of what seems to be essentially your personal aesthetics.
This is assuming intent that wasn't there. At the time, I did not know the author's view was so extreme that they view bumping patch on api additions is equivelant to bumping minor on api removals. I was giving the author the benefit of the doubt. I still feel discussing whether the same language for describing both is appropriate as I assume there are enough of us unaware or that don't care about the patch handling that it it is misleading to equate them.
> The rest of the article answers that: if you bump minor on all API additions, you allow others to specify a caret version with a minor version number, as the spec says they should be able to.
The spec says nothing of version requirements.
If you are trying to say "you can accept any patch", thats not true. The semver spec says you can add functionality so long as the API is unchanged. This is setting aside relying on bug fixes which is frequent enough.
This is one of the main reasons I hate this trend of small libraries: I don't want to have a complex web of dependencies that I am the first person to ever test in this particular version combination.
Give me Apache Commons any day of the week, with a single unified version for hundreds of classes that I know for sure work together among themselves.
If I need to bump a version with Semver, I can estimate how long it will take. Going from log4j 1.2.17 to 2.17.1 will be bigger than going from 2.16.0 to 2.17.0. And 2.17.0 to 2.17.1 should be easy.
I completely agree with your last paragraph, though. I'll take a collection of utilities from a well known organisation over many tiny dependencies any day.
https://www.stackage.org/snapshots
Without such a curated set of packages I've eventually run into unresolvable dependencies with ever package manager and language used by me.
At least for my experience in the Rust community:
- You can trust people to not break things. For the unsusual cases, people are quick to respond by yanking.
- Once people hit 1.0, they rate limit their breaking changes to be less disruptive
The nice thing about small libraries is each incompatible upgrade is a smaller step which makes it easier to take on. I've seen large code bases struggle to adjust to large breaking changes and hold off until it becomes business critical.
You can get a conflict only if someone very specifically required an incompatible lower version, like "<1.2.17" or "=1.2.16", and only if the crate with that requirement could not be avoided by choosing different versions of dependencies. In practice such locked version ranges are used rarely and temporarily, because they are known to be disruptive.
Is this not a problem with other versioning systems? Like Apache Commons?
Semver looks like a promise “upgrade from 1.5.0 to 1.17.5 is always compatible” but this promise can still be broken, with probability dependent on the depth and breadth of your dependency tree.
It’s frustrating to see this promise broken when you find an issue, write a test, downgrade the dependency to semver-compatible version, and the test passes.
We will accept that LibA works with LibC 1.2.3 perfectly, and LibB works with LibC 1.3.5 perfectly. But nothing guarantees that LibA will work with LibC 1.3.5, and this is where the problem starts.
In contrast, if LibA, LibB and LibC are all bundled into a Lib-Commons, I have good reasons to think that depending on Lib-Commons 2.4.6 is not going to produce unexpected incompatibilities between Lib-Commons.LibA and Lib-Commons.LibC.
Yes. When you add a patch then increment the semver patch version number. When you add a public function then increment the semver minor version and reset the semver patch version to 0.
If you're doing something else, then you're not doing semver, and IMHO you should highlight that to your users.
Only if your version is greater than or equal to 1. Before that, you can do what you want.
Major version zero (0.y.z) is for initial development. Anything MAY change at
any time. The public API SHOULD NOT be considered stable.
https://semver.org/#spec-item-4> Initial development releases starting with "0.y.z" can treat changes in "y" as a major release, and "z" as a minor release. "0.0.z" releases are always major changes. This is because Cargo uses the convention that only changes in the left-most non-zero component are considered incompatible.
This makes it more useful for OSS that never seem to reach 1.0.
I'm in the `serde = "1"` camp myself, even though I know that I "ought" to be doing `serde = "1.0.whatever-versions-docs-I'm-reading-right-now"`, but I don't because that's not right either. If I start a new project today, open the docs of serde and see that it's at v1.0.100, sure I could write `serde = "1.0.100"`. But then when I open the docs the next day to look up another function to use, and serde is at v1.0.101 now, does that mean I have to defensively bump it to 1.0.101 now? If that was the case then every code commit will involve bumping every dependency constantly.
The only way this can work is if my local build / CI compiles with `-Zminimal-versions` and actually tells me a certain version is wrong.
(Note that even for nightly users, `-Zminimal-versions` is unusable because it also applies to transitive dependencies too, so you're at the mercy of your dependencies specifying their dependencies correctly, which none of them do.)
Serde specifically had an issue with exposing and renaming some APIs that were meant to be private and got used in other crates (let's not even get into how that relates to semver, and whose fault was that), so IIRC serde 1.0.123 is the lowest version you can safely allow these days.
This does make me interested in adjusting `-Z minimal-versions` to get it useful. The biggest problem is depending on all your dependencies to be good citizens and test it (some refuse) or patching your deps (I'm too lazy). There is an idea floating around to make it only apply to your direct dependencies.
Say 1.1.0 adds a new function, and 1.1.1 fixes a bug in an old function. I write some code which uses both functions, and would hit the bug before 1.1.1. This code has to be used with >=1.1.1, or it won't work. If it's used with something <1.1.0, it won't compile, and if it's used with 1.1.0, it will compile but not work; the distinction between those clearly isn't significant. So in practice, 1.1.0 -> 1.1.1 is as meaningful an increment as 1.0.0 -> 1.1.0.
Semver would have been better with just major and minor versions.
This is something that Apple sometimes does for security updates on macOS: they'll release a new patch version on all supported versions.
It may be that the function only has an error if the caller passes `argc = 0`, but there's no way your code will ever call that function that way.
Bug fixes tend to be for weird edge cases. If the normal functionality doesn't work, someone would probably have noticed for the 1.0.0 release, or at least before the 1.1.0 release. And most of the time callers not only don't hit, but will never hit, the weird edge cases - that's what make them weird edge cases.
That said, given enough users, Hyrum's Law comes into effect where not only do some small percentage of users hit your weird edge-case bugs, but a small percentage of those end up relying on them.
Most people won't care about adding a stray function for one person. Major new functionality is different. Yes, this mixes "sentimental versioning" in with semver.
People tend to rate limit their major and minor versions, waiting until they get enough changes to "justify" it. While this can have a negative effect for small additions, I think its good to rate limit deprecations which are important for minimizing the impact of breaking changes.
Next on News At Ten, we ask "Is the Pope Catholic?"
It would be helpful in understanding what this post is about if this would have been linked somewhere in it.
Essentially there are a few popular Rust libraries that add functions in patch versions (or have done so in the past). This makes it necessary for users of that library to include the patch number when specifying the minimal version that they need because they can't assume an earlier patch will include all the functions they use.
The original forum poster said such libraries were in "violation" of the semver.org spec. This blog post disagrees on using the term "violation" because this is a minor issue (no pun intended... or was it).
It reads like a rambling at this point.
In the Rust community, there is tooling and culture around Major that we pay attention to that. Otherwise, I've seen no one care about minor vs patch until now, which is what led me to write this post.