Committing to a Rustfix Tool for All Epochs/Checkpoints Changes

A central point of the whole epoch/checkpoint idea is that:

Existing code continues to compile, and crates can freely mix dependencies using different checkpoints.

You're not forced to update your crate. Some concerns stated in this thread are a bit exaggerated.

This is exactly what we've guaranteed from the beginning! Code from the 2015 epoch that compiles without warnings will compile on the 2019 epoch.

A lot of this thread feels like retreading what we've already committed to about epochs, so I'm worried this isn't getting communicated effectively. To reiterate:

  • Crates from multiple epochs can be compiled together.
  • If it compiles without warnings on epoch N, it compiles on epoch N + 1.
  • We will provide a tool to automatically move your crate to the next epoch. We are considering how feasible it is to automate each deprecation we are considering as part of the decision.
  • The epoch can only change surface syntax, not the core semantics of Rust.
3 Likes

That’s a good point. I’m not generally opposed to epochs, but in my optimal world we’d have a couple of epochs and then leave the epoch system be and not add any more epochs after a certain point.

This is similar to what I had in mind. I don’t think we could ever commit to never discovering a new compelling reason to do an epoch, but we only have a handful of good reasons for one today, and we probably won’t discover any new ones until we’ve stabilized and/or implemented a bunch more features (which will take a long time). My assumption thus far has been that once either or both of the two epoch-using RFCs currently under discussion get accepted, we ensure a flawless rustfix workflow for them, and then–with the possible exception of the autoloading modules eRFC we’ve been “promised”–we probably won’t do any more epochal changes for the forseeable future. Not because there’s some kind of limit, but because we don’t yet know of any other epochal change worth doing.

Thank you for the "@" mention on this thread and on GitHub!

I really ought to write up a blog post summarizing all the Rust code at Faraday. We have open source stuff like cage, scrubcsv, catcsv, our BigML bindings, and the xsv partition subcommand we contributed, but also 4 internal projects. And new ones are planned!

This is a bit long, and the first part is basically background on where I'm coming from.

My background as a code maintainer

Let me share a few real-world experiences of code maintenance that I've run into, just so you know where I'm coming from:

  • The longest-lived code base I've ever maintained had comments dating back to 1987 and Win16. I'm not sure if it started out as C or C++, but it was certainly C++ by the time it supported MacOS Classic and Windows 3.0. I inherited it 2002, and it looks like I'll be doing some non-trivial enhancements next year. Over the years, this code base has earned over 10 million dollars, it has supported a 15-person team, and (last I heard) it's probably helping pay for somebody's retirement.
  • I've spent years doing consulting and freelance development, and I've gotten to see lots of musty corners of real businesses. I've seen Delphi code from 1995 that hadn't been moved to a new compiler in 20 years (because of a poor ABI story for 3rd-party binary components). This code was running in production and still making piles of money. I've also worked on a huge Rails 2.3 code base that was a major cash cow for a company, and which was paying them to pivot into a much larger "addressable market." They had 100% of their in-house developers working on the new app, and I was working on the legacy app.

The vast majority of these companies had real programmers. Most were successful, often with 50 to 150 employees and significant revenue. They would adopt new technologies if a sufficiently senior developer made a good case. But at the same time, they've been burned a few times. Generally, they had tests, but never as many as they wanted. And in nearly all cases, they've employed enough programmers for enough years that they have tons of code—far more code than they can actively maintain at any one time. Typical code might only be touched once every couple years.

I would describe these companies as a mix of early adopters and early majority (on both sides of the "chasm"), but almost never late adopters.

Lessons I've learned from maintaining code

While working with these companies, I learned a number of things about maintaining old code. Not all of these lessons are directly applicable to the epochs proposal:

  1. The biggest nightmare is always unmaintained 3rd-party libraries. As a user, you've never seen the code before, and you may not never have even called the library directly. So now you need to git clone the dependency of a dependency, read all the code, migrate it to the latest system, and then maintain the fork indefinitely. This gets depressing very quickly. Imagine, say, 200,000 lines of 3rd-party libraries, many of which haven't been updated in 5 years, all of which need to be migrated.
  2. Compiler updates can hide nasty surprises, even if the language doesn't change. I think my favorite example of this was a 3D simulator based on the GPLed Quake II engine. When updating to the latest MSVC++, we started seeing segfaults in this code. It turned out that MSVC++ had started performing tail-call optimizations on Carmack's C code, but that MSVC++ hadn't noticed that a pointer to a local variable had escaped somewhere deep in the "binary space partition" code. That was fun to debug—we had to single-step through the assembly before we saw the broken tail call.
  3. Over the course a decade, there's a certain amount of inevitable churn in even the stablest APIs. For example, the C gets function is nearly impossible to use correctly or securely, and it has been heavily deprecated. MSVC also deprecated a bunch of common C string functions in favor of more secure replacements. Some of these updates involved visiting a lot of old code. Similarly, there were migrations from 16 to 32 to 64 bits, and from classic Mac to Carbon to OS X. These are never fun, but over the course of decade or two, they're hard to avoid.
  4. The C++ language has actually had a very good compatibility story for a long time (although I'm not familiar with the latest updates). It's perfectly possible to run old and musty C++ code almost indefinitely. Even though modern idioms have changed multiple times, the old stuff still works almost unchanged.

Some suggestions for Rust

First, I think that "as backwards-compatible as C++ or Java" is actually a pretty good starting point. Both languages are widely used in industry, and they have dealt with the realities of multi-million line code bases and limited maintenance resources. If Rust can do better, that's great. But if Rust does worse, that could quickly make me nervous. Probably Rust needs to do at least slightly better than C++, because Rust pre-1.0 had a reputation for breaking things constantly. I think we're on track to get there.

Second, I think the most important goal is having an iron-clad story about (1) above. Any time I run rustup update, I want it to Just Work. In particular, every time I need to fork some unfamiliar library 2 or 3 levels down my dependency chain, I'm going to be deeply unhappy. Here is where multiple epoch support is enormously helpful.

Third, even with multiple epoch support and a good rustfix tool, breaking working code is still a big deal. Basically, if you have 100,000+ lines of code and a small team, the only viable strategy is to ignore most of the code most of the time. Basically, every time a language update breaks my code, it feels like the compiler team is making my life more difficult. :slight_smile: Even if the only "breakage" is examples on Stack Overflow, or needing to remember two different module systems.

"Flawlessly" is definitely overkill. If nothing else, things like the tail-call optimization bug in MSVC++ are going to occur, which gives you a certain baseline level of pain, and anything below that is basically tolerable. A more realistic standard would be "We used crater to run rustfix over crates.io, and the damage was very small." And of course, just running crater normally (without rustfix) protects against many other kinds of badness. But if you run rustfix over all of crates.io and it's a disheartening mess, well, it's going to be a disheartening mess for everybody's internal code, too.

Trust issues

And this gets us down to the hard question. Let's assume that multiple epoch support is great, rustfix can fix virtually all the code on crates.io, and somebody has thought through how cross-crate macros affect epochs.

But this still leaves the fact that I'd rather have backwards compatibility than a "perfect" language. Epochs should be used sparingly, for high-payoff improvements. The latest module system proposal is a decent example, largely because it's addressing one of the biggest flaws identified in user testing. But for any lesser issue, I'd be even more wary.

This actually gets to the heart of my objections: The epoch plan is hard to communicate without making many Rust developers nervous. You're explaining the epoch plan over and over again to the kind of people who read Rust RFCs. I'm going to have to explain it over and over again to people at Faraday. And there's going to be a whole bunch of developers who only remember, "Rust used to break all the time", and who see it breaking again.

This is why I think any epoch breakage should be as small and painless as possible. If people don't notice it, and if they don't need to spend more than 15 minutes dealing with the new epoch, then maybe we can just pretend the epoch didn't happen. :wink:

One good strategy might be to make the first epoch as painless and conservative as possible.

Interesting. I would guess that if we accepted two breaking changes now, then people would probably propose six next year. Maybe I'm just too cynical about human nature! But it seems to me that every language always has a long list of perceived flaws. And I feel like declaring "open season" on fixing perceived flaws is a bad precedent. I'm not saying that no flaws should ever be fixed, but that most "flaws" should probably be left alone unless they cause specific, clear harm to the adoption of the language.

18 Likes

Ironically, I view the module proposal as the opposite. A problem with teaching the language turned into a plan to change the language without actually making it easier for new people to understand. But that's just my opinion.

3 Likes

I’ve posted an update to the epoch RFC that spells out the policy around allowed changes in much greater detail, and in particular proposes a relationship to rustfix. Please have a look and leave thoughts on thread!

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.