On Semver
Along with the Trillium 1.0 release it seems worth discussing what 1.0 means in rust and in this project, as well as publishing a set of clear project-specific expectations going forward. TL;DR: 1.0 is just the first major release, but hopefully not the last.
If you look at your Cargo.toml files, you'll find quite a few rust projects that have been around for many years and are on 0.y.z and have mature release cadences and practices. Meanwhile, there are crates that treat 1.0 like an indefinite promise of stability, and it is a true rarity to find crates at a version greater than 2 or 3.
What is the cost of this? A whole range of expressive capacity --- when a package is 0.x, there is no notion of distinguishing "semver-compatible, but introduces new features" from "a tiny bug fix or documentation correction." Our communication channel is limited to a boolean "did it break or not." Post-1.0 crates get the full semver triple of compatibility communication: A breakage signal as well as a feature-vs-bugfix signal. Additionally, it artificially communicates software immaturity far beyond what is reasonable.
Here's what the semver specification says about 0.y.z vs 1.y.z:
- Major version zero (0.y.z) is for initial development. Anything MAY change at any time. The public API SHOULD NOT be considered stable.
- Version 1.0.0 defines the public API. The way in which the version number is incremented after this release is dependent on this public API and how it changes.
and
- Major version X (X.y.z | X > 0) MUST be incremented if any backward incompatible changes are introduced to the public API. It MAY also include minor and patch level changes. Patch and minor versions MUST be reset to 0 when major version is incremented.
What this means for trillium
In light of the above, trillium is going 1.0 with the current release instead of 0.3, but with slightly different intentions than the rust community's proclivity to stay at 0 or 1 indefinitely.
In brief, breaking changes (semver-major releases) will:
- happen
- be infrequent
- have a clearly communicated upgrade path in release notes
- be preceded by a semver-minor deprecation where possible
- be preceded by a prerelease window
Major releases will happen
Semver-major breaking changes are a normal and healthy part of even mature software evolution. The major version that comes after 1 is 2, and 3 comes after that, etc. 1.0 doesn't mean frozen forever, just that releases will be carefully managed and will avoid churn. Any major change is like an edition that you can choose to take if and when you want.
Major releases will happen infrequently
Nobody wants churn from their dependencies. Semver-major releases will happen often enough to facilitate software evolution and improvement, but not so frequently that it feels like a burden without commensurate value to stay on the latest version. Major release frequency will be determined by new functionality offered by the rust language, the introduction of new protocols that do not fit into the current interface shapes without breaking changes, or more generally a limitation in expressive capacity of the current interfaces and types.
I'm not committing to a specific release cadence aside from setting an upper bound on frequency: Breaking changes will not happen more frequently than twice a year. The four year interval from 0.2 to the next breaking release of 1.0 accumulated far too many breaking changes. I'd be quite happy if the interval between 1.0 and 2.0 were more like one year.
Major releases will have a clear upgrade path
Every major release will include a changelog with documentation of every breaking change and what will need to change in your code. Although I can't promise this, I intend to offer tooling to automate codemod-able upgrade paths wherever possible.
Major releases will be preceded by deprecation notices
At least two weeks prior to a breaking change, a minor release will annotate any removals with #[deprecated], to the extent that the rust language allows.
Major releases will be preceded by a prerelease, with an open window for feedback
At least two weeks prior to a breaking change, a prerelease will be published to crates.io, providing a window for users to ensure that their applications can smoothly upgrade and provide feedback if not. Prereleases will be accompanied by the upgrade documentation as described above.
Major versions will receive security updates
For some as-yet determined period of time (at least a year) after the release of the next major version, previous versions will get dependency updates and security patches that do not require breaking changes.
Reexport semantics for trillium
I'd like to take a slightly different interpretation of reexports than the ecosystem generally does. Every trillium crate will reexport every external type that it includes in its interfaces. If a trillium crate accepts a url::Url, it will always reexport Url. Trillium's semver guarantee covers its reexported types. A breaking change in an upstream crate only becomes a trillium breaking change if it affects a type trillium publicly reexports. Please use reexported types for stability. If you add a direct dependency on a transitive dependency of a trillium crate, it is your responsibility to pin versions appropriately.
For example, trillium has a dependency on trillium-http, the lower-level http implementation. trillium reexports a number of types from trillium-http, such as Headers, Status, Version, Method, etc. If trillium-http requires a breaking change to a type that trillium itself does not publicly export, that will not entail a breaking change to trillium.
This reexport policy is unconventional. I'm establishing it because it aligns with how I think most rust users think about API boundaries, and I'll adjust based on what we learn in practice. If you're a trillium user and this causes problems, I want to hear about it.
Conclusion
Trillium 1.0 means a commitment to a mature release cycle and fully expressive use of semver as defined at semver.org, including in ways that slightly diverge from the rust community conventions. I encourage other maintainers who read this to reconsider their own versioning strategy if they maintain a crate that has been 0.x or 1.x for longer than a few years.
