Too many low-level crates are still at 0.x.x and unstable

Some low-level crates that ought to have stable APIs, don't.

I just got hit by an undocumented breaking API change to "simplelog" from 0.9.0 to 0.10.0. You can now set the color of the log messages, using a new parameter added to an old function. Very important feature. Broke three programs of mine.

Previously I've been hit by a change from "UDP" to "Udp" in a networking package.

Looking at some crate versions, I see:

  • argparse 0.2.2. Argparse is an implementation of an ancient Python package. Ought to be stable by now.
  • sha3 0.9.1. Planning to change SHA-3 any time soon?
  • glam 0.13 Glam is a math library - short vectors, matrices, etc. Should have a stable API by now. Matrix multiply had better not change. There were some breaking name changes a few months back.

Such low level stuff needs to make it to 1.0, or just forced to 1.0, for stability.

3 Likes

This is endemic, and probably requires a sea change in how we treat open source developers to resolve.

Big honking edit: Your frustration with breaking changes, not clearly communicated and at your expense as a user, is absolutely valid, and I encourage you to talk to the maintainers of those libraries to share with them how their choices have affected you. Communication solves many things, or at least raises awareness. End big honking edit.

Under semver, moving to version 1.0 represents a strong commitment: it's the first major version where the author promises not to make a breaking API change. Up to that point, during the zero major version, the author hasn't made that promise and probably hasn't even had to think about the compatibility implications of changes. Adopting an API "permanently" is a high psychological and social barrier, and demands additional work from then onwards, in perpetuity, or at least until the library is no longer maintained at all.

Most of the people you're implicitly asking to commit to an API would be making that promise entirely at their own cost. We do not, habitually, pay these developers, or volunteer our time to audit changes for compatibility risks, or otherwise offer much in return for a developer who releases version 1.0. We only ask that they take on additional work, that they're excused from so long as the version number starts with a zero.

Semver at least makes this explicit - zero versions are expressly permitted to have breaking changes at any time. Changing dev culture to consider compatibility versions from the first time code is released to users, rather than when it's "done," might work, but that's a long term social engineering project. In specific cases, appealing to the author to release version 1 might work, but only case by case.

The Rust community is in absolutely no position to dictate to library authors that their crates must go to 1.0.0 or that an author must do uncompensated work. If you want to take on that work, in many cases you're free to - all three crates are MIT licensed, so you can release nagle-argparse 1.0.0 if you so choose - but I can't support the idea that tailhook should be told that if they don't release 1.0.0, someone else will and the crates.io registration will be taken away, for example. And, by the same token, we absolutely cannot demand that you rely on development releases of anything, either.

18 Likes

I just want to note that glam specifically is explicitly still beta software. While math isn't changing, our understanding of how best to expose an implementation through API, as well as how best to implement the math (and what implication that has on the API) is constantly evolving.

Plus, there's nothing specific magic about 1.0 that makes software "more stable" than 0.X. It's entirely a social construct. If a library wants to support old API surfaces, it can do so just as well at 0.12.4 as it can 12.3.2.

It's more important for vocabulary crates to be stable than implementation crates which are entirely encapsulated by their consumers. Shared, "public" dependencies need to have the same version to be shared. "Private" dependencies can be duplicated at a minimal cost of object code duplication.

I definitely agree that any crate would benefit from keeping a changelog of new features and breaking changes, though.

And if it matters so much to you: pay developers to get these crates to 1.0 and support them. Please don't demand more unpaid labor from people already providing their work for free.

I just want to add that while this is true of strict semver, Cargo uses the "0.MAJOR.PATCH" extension to semver: within a given 0.X version, versions are considered API compatible, as with major versions >=1 in normal semver.

If someone publishes API breaking changes in a 0.X release rather than a 0.X+1 release, they've broken the social versioning contract encouraged by Cargo and CratesIO, so that should be brought to their attention, to potentially yank and fix (probably by reversioning to 0.X+1).

There's obviously no requirement to stick to this, just like there's no requirement to stick to semver, to put meaning on version 1.0, or to maintain the crate or provide any warranty, express or implied.

17 Likes

Nah, that's not how it works at all.

Many (most?) contributors to Rust (the language and the wider ecosystem) are not paid employees; they are volunteers developing and maintaining crates in their free time.

Straight up demanding that they put more work in is not only rude, it's also far disconnected from reality.

As to just "forcing" crates to 1.0 "for stability", that's probably the worst possible solution technically as well as policy-wise. It's like deleting all unit tests that fail instead of fixing the underlying bugs. The pre-1.0 crates are not stable for a reason. They may have critical bugs, disputed API choices, questions around code organization, efficiency, scope, or are just generally too young to be eligible to be considered stable. Forcing them to stability with a potentially slow, inconvenient, incoherent, incomplete, buggy, or generally immature interface would do a lot of harm to everyone.

28 Likes

Another reason why a crate may not be at 1.0.0, yet is, because their public dependencies haven't made the jump to 1.0.0, yet. It may not be the most common case, but it's something to look out for.

P.S.: libc 1.0.0 when?

2 Likes

This is bush league. As an example, I created and maintain the aho-corasick crate. It's still not at 1.0 yet. But the Aho-Corasick algorithm is itself ancient. Your comment implies the crate should be at 1.0 simply because the algorithm hasn't changed. But this is just totally dismissive. And speaking as a volunteer, yes, I find this comment and your general approach here to be rather off-putting. You might want to reconsider your approach.

The obvious problem in your implication is that the idea of a thing (SHA-3 and Aho-Corasick) and the implementation of the idea of a thing (the sha3 and aho-corasick crates) are two distinct things. The former exists in the abstract, but the latter exists in the messy real world:

  • There might be constraints on how the API can be expressed given the language. I literally spent 10 seconds looking at the sha3 API before I realized that they're probably waiting on const generics to finalize the API. (Minimum const generics just landed.) Searching the issue tracker seems to confirm that to be the case.
  • In theory, theory and practice are the same. But in practice, they are totally different. In theory, the Aho-Corasick algorithm is an time-optimal multi-pattern substring search algorithm. But in practice, it isn't the fastest thing depending on the number and nature of patterns. This doesn't always have an impact on the public API, but sometimes it does.
  • The Aho-Corasick algorithm, as described in the textbook, is of somewhat limited utility because of how it reports matches. The algorithm can be modified to match the prevailing expectation from regex engines (both Perl-style and POSIX-style). But this has to be a part of the public API so that callers can choose what kind of semantics they want.
  • In theory, you rarely need to deal with such arcane matters as to whether your haystack fits in memory or not. But when you're building a practical implementation of this, you might want to offer streaming APIs, which tend to look different from their in-memory counterparts, largely for practical reasons.

I'm picking on aho-corasick here because I know it well, and I know just how off-base your approach is here because of it.

This is a somewhat misleading characterization of what happened. Cargo treats versions whose leftmost non-zero digits are distinct as semver incompatible. So that version bump from 0.9.0 to 0.10.0 was communicating that there was (maybe) a breaking change. Unless you were using simplelog = "*" (or similar) in your Cargo.toml, you should have had to specifically opt into that upgrade. A simple cargo update wouldn't automatically do it for you. So simplelog didn't break you, by the common sense of the word. While I understand it is frustrating when breaking changes aren't very clearly communicated, they played by the rules. When you do a semver incompatible upgrade, breaking changes are possible.

40 Likes

Yes, I was too annoyed. But low-level packages not making it to 1.0.0 stability remains a problem.

It's both a feature and a problem of Cargo that version numbers are preserved, and you can stay with old versions for a long time. Upgrade and fix breaking changes, or version lock and be told "oh, you should be using the new version"?

It is a problem indeed, at least problem in communication and perception of crates' stability.

I see two reasons for it:

  1. The Rust community puts too much pressure on commitment to 1.0.0 being stable, to the point it's almost treated as done and final. In theory one could release a breaking 2.0.0 a minute after 1.0.0, but nobody wants to do that.

  2. Change from 0.x.x to 1.0.0 is a semver-major change, and it's disruptive. If a crate waited too long to release 1.0, it may be painful to do so. The prime example is libcpocalypse: libc 0.2 is used in half of all Rust crates.

5 Likes

I think you'd also see a significant number of authors more willing to go to 1.0 as language features like HRTBs, const generics, and specialization are developed, as those can have a major impact on the design of APIs.

3 Likes

Yes, there are some crates that use horrible hacks for small integers and could use an upgrade.

If some of the lower level crates start migrating to 1.x.x, that would establish some momentum. There's a semver hack to help with that.

I'm wondering if anyone can point me at documentation of this? It's how I operate, and it makes more sense than "standard Denver" but clearly this distinction is confusing to some folks and it would be nice to know where to point them for understanding versioning in the rust ecosystem.

Here is how cargo interprets semver: Dependency Resolution - The Cargo Book

And what does/doesn't constitute a breaking change is specified here: SemVer Compatibility - The Cargo Book

4 Likes

On this topic, a while ago npm init changed from default 0.0.0 (or something) to default 1.0.0.

The idea from what I could tell is to get people past the mental block of "1.0 is done and final". It would be interesting to see what the result was there.

I can't imagine how version inflation would help. Like currency inflation people adjust, things to the new value. Only the old folks complain because everything has become so expensive. Version 1.0.0 becomes the new version 0.0.0 as it were.

To my mind the first version should be 0.0.1. After all 0.0.0 is nothing at all. An empty repository perhaps.

Of course one could argue that no such early effort should be published at all as it lightly of limited use and to be changed a lot as it evolves. On the other hand publishing such draft versions helps attract testers (users) and collaborators.

1 Like

The main thing is that people are happier with major versions having breaking changes since they're used to it. It also prevents the somewhat silly situations like the incredibly popular React getting to 0.14 before deciding that they have been production ready for a long time, so their next version is 15.0 (or was that 0.13 to 14.0?)

I thought the same, but recently realized why cargo starts (by default) at 0.1.0. Otherwise there is no possibility of a second release that is API compatible with the first, according to cargo's semver rules.

10 Likes

Good point.

For this reason I think 0.0.x makes sense as: this is still a prototype, don't use it yet seriously because I'm trying things out, it will completely change all the time, I don't even want to think about whether a change is breaking or not at this point.

1 Like

In principle this can be valid reasoning for any crate < 1.0.0.

Not with cargo's semver, though. 0.x.(y+1) is compatible with 0.x.y.