[pre-rfc] Stable features for breaking changes


Ah, finally found the epochs thread. Overall, I’m very glad this is happening—breaking changes to the surface langauge are inevitable. The use of years not versions strikes me a bit as a marketing gimmick, but I always avoided python and thus don’t have scars from it’s 2 to 3 transition.

One detail, that I assume will be fleshed out but would like to kick off discussing now, is the pipeline of unstable features into epochs. Currently all our unstable features are non-breaking, but with epochs we can start have breaking unstable features. I’m thinking the nightly should have a “next epoch” to queue up breaking unstable features we are considering stabilizing.

Less clear to me is how this works with the “all errors were warnings” rule. Do we need two next epochs? Separate warn and err versions of breaking unstable features?

Put differently, maybe it’s better to keep things simple on stable, but nightly will have to do much of what this RFC proposes either way.


One other question worth bringing up soon-ish is how libraries are supposed to support multiple epochs, if at all. I can’t think of any option besides the simplistic #[cfg(epoch=...)] answer, but we clearly need to decide if this is the sort of code we want people to write when impl Trait finally stabilizes:

trait MyTrait {
    // speaking of marketing gimmicks...
    #![cfg(epoch = "hermit-crab")]
    fn foo() -> MyIterType {}

    #![cfg(epoch = "mantis-shrimp")]
    fn foo() -> impl Iterator {}

We should probably make an actual epochs thread at this point.


This seems preferable to the current situation: I released a version of my library that uses features that are only in Rust 1.17, but the errors that people get when they compile my crate with earlier versions are cryptic and it’s far from obvious that the solution is to upgrade the rust toolchain.

For this reason, maybe it would be better that this mechanism isn’t used just for breaking changes, but is instead made available for all features. That is, when a feature is stabilized, don’t remove the #[feature] flag for it.


Ah right. This is the cricuial reason why we need versions not years: it’s important to track a minimum version whether or not that minimum version is a breaking chance from what came before. If we track breaking and non-breaking versions alike, well, we might as well use semver as we do already.


As I mentioned in a comment on the cargo schema RFC, I think that epochs and version numbers are two distinct things. That is, you may want to upgrade to a specific rust version number because of a bug fix, or to get access to some library routines. You may do this even though your code is still “old epoch” code.

In other words, it is not just a “marketing gimmick” (as you suggested earlier) to mark an epoch separately from a release number – there is a real technical distinction being drawn here. The rust compiler is still in the 1.X release because older code works just fine, without changes. If we called the new epoch 2.X, it might lead you to think that you cannot upgrade your compiler to Rust 2.1 just because you haven’t converted your library to the new epoch yet (but that would not be true).

(Nonetheless, you could imagine using version numbers for both the rustc release and epochs. For example, iirc, Java’s compiler allows you to compiler older code with a --version flag (e.g., if you use enum as an identifier, you would compile with --version 1.4 or something). There would still be two distinct concepts, presumably, but both would be versioned with a semver: the compiler in use and the “compile as of” version. The concern here is that this will give the impression of incompatibility where none exists; I am not sure if this is true or not!)


Right I was proposing something like your last paragraph. Certainly compiler version and surface langauge version can be distinguished—I’m arguing in that last post that the language version badly needs the richer compatability partial order that semver offers. (Ironically rustc, if we never break stable CLI or drop support for old versions of the langauge, doesn’t, but semver and the ever-present leading 1 don’t hurt.)

To me, since we already say “Rust 1.x”, not “rustc 1.x” the current version is felt to primarily denote the version of the langauge, and only secondarily the version of the tools. So it seems to me we already have epochs, and a new version should instead be forking off a separate compiler version.

Now, insofar that the compiler version is uninteresting because it induces a trivial compatability partial order, and the teams expressed a preference for fewer versions, one could just not bother with a seperate compiler version—call it “rustc for Rust 1–x.y” or something. Basically this a choice between pruning useless but valid extra information and countering “Rust 2” FUD. I’m happy either way.


I’d also like to add to @carols10cents explanations that the epoch-concept might severely hamper the development of competing compilers, as these may have to implement multiple language versions for ecosystem compatibility.