Let me try to lay out my thinking here for what the policy should be.
To start with, I still think we should make the change proposed in this RFC. I think this change falls into a category that I have been
calling (in my head) course corrections. That is, this change is not
really fixing a bug in the code: the code follows the RFC. It is not
a soundness concern. It's just that the RFC's design seems suboptimal
in retrospect, basically, and it would be great if we could fix it. In
particular, it'd be great if we could fix before there is a lot of
code "in the wild" that depends on the current setup.
Initially, I thought it would be best to address these kind of course
corrections using a version-based opt-in (as RFC #1122
specified). I've changed my mind, and I'll explain why later. I now think it would be best to say that course corrections are permitted in a minor release, but only if the effect is judged to be negligible. I am sure we will evolve better ways to estimate the
impact of a change over time, but for the moment I would propose the
- The change has minimal impact on crates.io.
- Because crates.io doesn't represent the entirety of Rust code, the
feature must be newly stabilized in the previous release, so there
is not a lot of (stable) code out there depending on the current
- There must be no other way to fix the problem.
I think that this proposal meets these criteria. In an ideal
world, it would be the only case that will ever meet those
criteria. This is because in the future I hope that we refine the
process such that we catch problems like this during nightly or alpha
builds. But I wouldn't rule out that a comparable scenario will arise
in the future (and our decision in that case will clearly be informed
by the eventual fate of this RFC.)
Why not opt-in?
Some of you are probably wondering why I think it is better to make a
breaking change here. After all, I initially advocated using a fine-grained opt-in mechanism. In that case, we can change the
language, but existing code is unaffected. Huzzah, everybody wins!
(Right?) But I am now growing suspicious that this "free lunch" is in fact something of a mirage. Even if no code in crates.io stops
compiling, backwards incompatible changes still carry a cost, and that
cost grows over time as the amount of Rust code grows:
- When we authorize an opt-in change, even if all existing code
continues to compile, it still causes bitrot in tutorials, stack
overflow answers, etc. People's memory of how the language works
will also have become inaccurate, which can be confusing if you're
not following Rust developments closely.
- It also means that people's notion of how "Rust 1.x" behaves
will be fragmented: 1.22 might be different from 1.21 in relatively
minor ways, which seems confusing (it seems natural that 1.22 will
have new APIs and new features, but small changes to otherwise stable
designs seems less intuitive).
- We are going to have a lot of releases. I predict that maintaining this version number
on crates will be a constant annoyance. Quick: Do you want to tag your crate with
Rust 1.16 or 1.17? If you choose wrong, somebody will probably complain to you because
they are using Rust 1.16 and it works just fine, so you should lower your version number.
- The caveat here is that there are good reasons to want to avoid newer APIs and
maintain compatibility. I think we can handle a lot of that sort of thing via
cargo and automated analysis.
Put another way, I am concerned that having the ability to "opt in" to course corrections is a problem because it encourages us to make those corrections on a regular basis. rather than just in the most
dire of cases. Those corrections will seem painless at first, but in
fact the solution will have hidden costs that come up later. The cure
is worse than the disease.
By making course corrections the exception, and not the rule, I think
we will be better off overall. Moreover, it will minimize tutorial rot
and other things, because any feature that is heavily or widely used
will not be eligible for being changed (whereas with opt-in, we can
make changes in a much wider range of circumstances).
Now, of course, this proposal has to fit into a larger strategy on
versioning. In particular, we've not really addressed how to make backwards incompatible extensions. Nor have we addressed when it
makes sense to issue a new "major version" of Rust (e.g., Rust 2.0),
and what kinds of changes we might do as part of Rust 2.0. Here too,
I've been evolving my thinking. Initially, I hoped to avoid a new
major version as long as possible, so as to ensure that code kept
compiling for as long as possible. But I now think that this is
leaving an important tool in the toolbox.
Major version numbers should be our way to signal "chapters" in Rust's history. These chapters might include backwards incompatible
changes, but they do not have to, and in fact ideally they would not
(because we want code to continue compiling whenever
possible). Rather, Rust 2.0 can serve as a signal that a lot of great
stuff has happened since Rust 1.0, so if you haven't been paying
attention, it's time to take a look. Put another way, even if code
from the early Rust 1.x days still compiles, it should feels dated
when Rust 2.0 is released, because it is not taking advantage of new
features and new idioms.
I think, in practice, releasing a major version of Rust will feel
analagous to the infrequent releases of other programming languages,
such as the change from C++11 to C++14 or Java 1.4 to Java 1.5. The main difference from how other languages do things stems from the train model. This means that we can introduce those new changes more
gradually, in the 1.x line, and only declare 2.0 when the full set is
available. If some of the
2.0 features are backwards incompatible, this implies that sort of
feature-based opt-in during the 1.x line (analogous to Python's
The advantages I see of this model is that it:
- promotes regular major version releases, which give us a chance to
highlight the exciting work that we've done (we can't really expect
Rust users in the future to be closely following the new features
in every minor release, much less prospective Rust users);
- gives a simple mental model for versions of the language, where "Rust 2.x"
code indicates major shifts;
- doesn't require people to tag their code with fine-grained version
numbers like 1.17, which I foresee being a constant annoyance. It
might be necessary (or useful) to tag with "Rust 2.x", but that
seems much easier to understand.