Crates.io curation

That does sound like a nice idea, though considerably more heavyweight of a solution! Sitting down to write good long-form documentation is one of the harder parts of open source. We started something similar for the Tracing project (sample: tracing-book/internals.md at main · jswrenn/tracing-book · GitHub).

I agree (and probably others do as well) that tribal knowledge helps no one. It would be great if it were (somehow) explicit what crates are “stable”, just not sure what the best way is. Also, there’s nothing anyone can do to force maintainers to go to 1.0; it’s at their discretion how they want to run their project. Though writing up some recommended guidelines around stability guarantees that maintainers can choose to follow more closely would be nice. Is there not such a doc floating around already? I haven’t seen one.

1 Like

Yes really. You said, "get the no breaking changes guarantee." That's different from some nebulous idea of "stability." 0.x.y in the Cargo ecosystem is considered semver incompatible from 0.(x+1).y, in precisely the same way that x.y.z is semver incompatible with (x+1).y.z. So you get the "no breaking changes" guarantee regardless of version due to how Cargo implements semver.

I understand you want people to 1) conform to your definition of stability and 2) signal it by releasing 1.0. That's fine. I think that kind of advocacy is good. That is not what I'm commenting on here. What I'm commenting on here is a factual inaccuracy in what you're saying. My guess is that you're flubbing the words. I am not trying to express an opinion here.

13 Likes

The exact same semver semantics apply in cargo to 0.MAJOR.PATCH as MAJOR.MINOR.PATCH. There is a bias to assume that 1.0 will last longer before 2.0 than 0.8 will last before 0.9, but this is a fallacious assumption. Any package can bump the first version (and drop support for old major versions) just as easily as if they're publishing a 0.X version. The difference (in cargo semver but not in upstream semver) is purely a social one.

This social reason is in fact reinforced by how semver is used by the majority of the crates ecosystem. Upstream semver (in part because they use anything-goes 0.X rules) recommends that roughly if you have any consumers — if you'd ever delay a change because it's API breaking — you should not be 0.X. However, major ecosystem crates like hyper use 0.X versions for packages which are stable enough to use in production and do defer API improvements due to being API breaking. The difference being communicated is very-long-term stability, on the order of “foreseeable future.” (Usually not “forever” — 2.0 is still available — but at least on the order of multiple years.)

This is, ultimately, a completely social problem. What “1.0” means is different to different people, and publishers in the crates ecosystem bias towards putting code out early and near unreasonable standards for 1.0. (For things involving cryptography, like HTTP, version 1.0 likely is expected to come with security releases in near perpetuity, even if a 2.0 is released.)

(That said, there is an ongoing push making progress for hyper@1.0, so perhaps that's not the best example crate to use here.)

But this is still a problem that lib-rs offers a (partial?) solution for! Compare the page for hyper to that of fmod-rs (a package I am actively working on and reserved the name for). fmod-rs is marked on the page as unstable. blake3 has 25 releases, 5 of which are considered stable.

If you want more crates to reach 1.0 quicker (roughly: when they're stable enough, rather than when they're fully stable), then what I'd suggest is getting together a number of major crate publishers in a coalition to publish a manifesto whitepaper defining recommended practice for:

  • What does it actually mean for a crate to be 1.0?
  • What makes a crate “1.0 ready?”
  • What makes a crate not “1.0 ready?”
  • How should 1.0 crates provide API extensions which aren't “1.0 ready?”
  • When should crates prefer a worse but more stable API[1] over a better but less proven API?
  • How can 1.0 crates minimize the cost of releasing and migrating to a 2.0 release[2]?
  • How should crates advertise the expected trigger/timeline of a 2.0 release?

These are all entirely social problems solvable independently of cargo support. Features like stability attributes for the rest of us can help with this, but under the same ideal of “worse today rather than better tomorrow,” can be polyfilled (link is to such a polyfill pattern).

There are potentially more questions you could add onto the charter there (e.g. when is it worth separating out a more stable lib-core (e.g. for interopability traits) from a less stable main lib), but I expect those questions (and answers) will fall out of trying to answer the above.

If you really want to formalize this, spinning up a Project Group with the above charter seems fully reasonable. I don't know if T-libs[-api] would consider this in-scope (they're mostly focused on the stdlib supporting the ecosystem side of things), but even if it isn't, following that structure could make sense.

I would be willing to help organize/triage/admin/etc such a project group, though in doing so would want to explicitly abdicate any say in the actual policies recommended by the group and be purely administrative.

And of course, any conclusions of said group would necessarily be purely best-practive recommendations. (However, they could end up as part of the official API guidelines.) It would be up to individual crate maintainers to follow them (or not) and to say if they're following them.


  1. A great example here is using a “many modes GAT” versus just having multiple copies of the API and implementation. More generally, at a minimum any API that could be made better by new upstream (language or library) features, but can be (perhaps poorly) emulated today with limitations. ↩︎

  2. The existing de-facto practice of “just never release a 2.0” is what leads to the effective use of ZeroVer. ↩︎

8 Likes

The actual language is

This guide uses the terms "major" and "minor" assuming this relates to a "1.0.0" release or later. 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.

So a release below 1.x.x is considered an "initial development release". The problem is that too many crates are still initial development releases.

Yes, cargo uses the convention that a change in the left-most non-zero component indicates incompatibility. This is not really relevant to the problem of crates being unfinished but widely used.

I didn't say it was. I don't know how much clearer I can be here. I was pointing out a factual error in something you said. I was not trying to engage in your advocacy for crates to push 1.0. (If you want to talk to me about that, you can file an issue on the tracker for the project you want to be 1.0. Others have tried.)

1 Like

Being painfully explicit: this means that your assertion

Is factually, objectively incorrect.

“Initial development releases” have the exact same

that post-0.X releases do.

The only[1] difference is a social one of it being an “initial development release” or not.

You won't run out of major versions to make breaking changes in.

I will also reference again my previous post: the best way to get more crates to 1.0 will be to gather publishers together and come together on a shared definition of what makes a crate an “initial development release,” as well as maybe more importantly what makes it not one. Such work will likely also have to provide recommended techniques to allow crates both with “generally stable” and “initial development” features/APIs to coëxist[2]; the Rust/crates ecosystem (especially 0.X crates) tends to favor making APIs available before commiting to their stability.


  1. Well, actually you do gain the ability to distinguish between MINOR and PATCH releases. However, PATCH releases are quite rare in the first place; most patches come with additional API capabilities, making them strictly speaking MINOR releases. ↩︎

  2. And, ideally, prevent OSSification (defacto stabilization) of “initial development” APIs without undue burden to opt in to testing. (e.g. how using unstable std APIs requires opting in to the nightly toolchain, but doesn't require explicitly opting in to each #![feature] enabled by dependencies. Something like the RUSTFLAGS="--cfg procmacro2_semver_exempt" environment variable works to require explicit acknowledgement of the use of unstable APIs, but has to be repeated for each and every dependency-of-a-dependency using this technique, rather than being able to encapsulate one unstable API surface inside another unstable API surface (but not in a stable one).) ↩︎

1 Like

One great thing about this discussion is that I’ve learnt a lot about how much lib.rs offers that I didn’t notice or understand earlier, which is going to make finding libraries easier in the future. Thank you @kornel for all your hard work!

11 Likes

I agree about the general philosophy of highlighting the project activity's sanity.

I am not convinced that versioning should be motivated by a badge or some other external sign of accomplishment.

1 Like

Me either, and I suggested it. Anybody have a better idea?

2 Likes

What is 1.0 but an arbitrary badge/label, though?

As has been stated previously in this thread, there is no semantic/mechanical difference between a 0.X.Y version and a X.Y.0 version.

The best approach is to ask the maintainers what the status of the library is w.r.t. new breaking releases and production-readiness. This is a social problem and there is no shortcut to getting maintainers to make this information more publicly available.

lib-rs's metric (some ratio of breaking to patch releases) is about as good of a technical solution as you can get.

4 Likes