How to speed up the Rust compiler in 2018: NLL edition
blog.mozilla.org
blog.mozilla.org
NLL is on by default in the Rust 2018 mode of the Rust 1.31 release, shipping December 6.
NLL will be enabled in Rust 2015 sometime early next year, we do not have a precise release yet.
Wouldn't that be 2019 not 2015?
> Rust ships releases on a six-week cycle. This means that users get a constant stream of new features. This is much faster than updates for other languages, but this also means that each update is smaller. After a while, all of those tiny changes add up. But, from release to release, it can be hard to look back and say "Wow, between Rust 1.10 and Rust 1.20, Rust has changed a lot!"
> Every two or three years, we'll be producing a new edition of Rust. Each edition brings together the features that have landed into a clear package, with fully updated documentation and tooling. New editions ship through the usual release process.
[1] https://rust-lang-nursery.github.io/edition-guide/editions/i...
If 2015 is the only one that won't be generally feature-frozen, it really should have had a different-looking name.
Edit: That blog post says "We’ll continue to add features to Rust 2018 after its release, just like we continued to add features to Rust after the Rust 1.0 release."
Is it being feature frozen or not? I'm confused.
So for example, Rust 1.31 will add the “impl_header_lifetime_elision” feature. It does not require Rust 2018, as it is backwards compatible. As such, it’s a “Rust 2015” feature, but it will work in both.
A feature like async/await requires the async keyword. New keywords are not backwards compatible. As such, it will require Rust 2018, and will not work in 2015. However, if we release “Rust 2021” in three years, it will still work in that.
Does that make sense?
And "specific to Rust 2018" is also an awkward term. From context I think that means the set of features that are part of Rust 2018 and also existed on its release day? But that meaning is not obvious.
Is there any other term that means "the Rust 2018 set of features, as it was on release day"? It seems like something that really should have a clear name.
> From context I think that means the set of features that are part of Rust 2018 and also existed on its release day?
The "also existed on its release day" is redundant; they have to be in place on release day. If they weren't, then they'd be breaking.
> The "also existed on its release day" is redundant; they have to be in place on release day. If they weren't, then they'd be breaking.
I don't know how to reconcile these two sentences.
If yes, then how do you best distinguish "Rust 2018 with additions" from "Rust 2018, the original 2018 version"?
If no, then there are some extremely misleading descriptions of what an 'edition' is, because they don't mention that 2015 is special.
You cannot, no.
> there are some extremely misleading descriptions of what an 'edition' is
Where?
I am sorry you're this confused. I don't think I know how to explain this to you.
"Beyond that, it’s also important to mention that this release will be the initial release of Rust 2018; in some sense, it’s the start, not the end. We haven’t formally committed to a schedule for editions, but it’s likely that the next one will be Rust 2021. We’ll continue to add features to Rust 2018 after its release, just like we continued to add features to Rust after the Rust 1.0 release.
"It’s also important to note that Rust 2015 is not frozen. Anything that does not require being a part of Rust 2018 will work on Rust 2015 as well. This is due to the way editions work; given the small nature of possible changes, the compiler uses the same internal representation for all editions."
That seems to directly contradict your first answer. Is there some subtlety I'm missing?
> You cannot, no.
So let's assume I think of a new syntax which is compatible with existing Rust 2018 code, but which is syntactic sugar for async code (so not compatible with Rust 2015 code). Are you saying that this can not be added until Rust 2021 comes along? Or are you saying that you can add it but it won't be Rust 2018?
If the latter is the case I think it's really confusing to call impl_header_lifetime_elision a Rust 2015 feature.
It’s really not clear to me what exactly you’re proposing, but it sounds like breaking syntax, and so yes, it would have to wait.
That sounds like Rust 2018 would get a new feature, something that sounds in contradiction with a feature freeze.
> It’s really not clear to me what exactly you’re proposing, but it sounds like breaking syntax, and so yes, it would have to wait.
I was trying to clarify things by proposing a particular situation, a non-breaking syntax change, so I'm not sure how that could possibly sound like breaking syntax.
If you believe it's impossible for syntactic sugar to be introduced with backwards compatibility then how do you explain the question mark operator for Error handling?
? isn’t sugar, and also isn’t a breaking change.
Because that's what you seem to claim when you're saying that Rust 2018 will be feature-frozen.
> ? isn’t sugar, and also isn’t a breaking change.
? is not a breaking change, but is a new feature. That's the whole point. (also it's syntactic sugar by any definition of that word that I'm familiar with, I'd be interested to hear what your definition is, but it's otherwise besides the point).
? isn’t syntax sugar because it doesn’t desugar into other syntax, it’s a feature in its own right. At least, that’s what I recall. Regardless, I don’t think it really matters.
It may be unlikely that such changes exist, but evidence from two different people has shown that it's at least something that can be considered (even if it's by people not familiar with Rust).
> At least, I can’t think of anything. Or at least, nothing that’s compatible.
Nobody asked you to come up with a che, the question was what would happen if someone did come up with such a change. The answer seems to be "it will be added" (assuming it goes through the normal process of course, I'm not claiming that a random change will be added merely because it's compatible), at that point you can no longer claim a feature freeze, because a feature freeze is a much stronger claim than merely saying "it's unlikely that there will be changes".
> We do have features for 2018 that are incompatible with 2015.
I'm fairly certain that everyone in this discussion understood that there are features in 2018 that are not in 2015, that's the whole point of having a separate edition.
But if you enable NLL in Rust 2015 mode, that code will no longer compile with Rust v1.0. Which means we're still back at square 1 (meaning each crate has to specify which compiler version it requires).
Could you help enlighten me on why this is safe to backport to Rust 2015?
With backward compatibility, old programs will work with new versions of the compiler (alternatively, for web browsers, "old websites will work with new versions of the browser" (or for operating systems, "old binaries will work with new versions of the OS" (or for game consoles, "old games will work with new versions of the console", etc.))).
In this case, code written as of Rust 1.0 should continue compiling with Rust 2015, respective of the compatibility guarantee ( https://blog.rust-lang.org/2014/10/30/Stability.html ). By this definition, Rust is backwards-compatible.
What Rust is not, and has never been, is forwards-compatible. Very few systems in the wild are forwards-compatible. A C++ compiler from 2003 is not able to compile C++17 programs, and is thus not forwards-compatible. Likewise, web browsers from 2003 cannot render web pages from 2018, Windows ME cannot run binaries compiled for Windows 10, PS2 cannot play PS4 games, and so on.
> meaning each crate has to specify which compiler version it requires
Every Rust crate that does not specify a compiler version is compiled in 2015 mode. It must be this way, for backwards-compatibility. However, when you run `cargo new`, cargo should automatically insert the directive specifying the most recent edition supported by your compiler. In this way, Rust can gradually push the ecosystem forward without any additional burden to the user and without leaving behind any code that doesn't update.
Hmm. For me, fast compile times have vastly more ergonomic benefit than NLL.
Well we weren't getting those anyway :(
Both compile times and NLL are, and have been, at the top of requests from production and hobbyist users alike for a long time now.
However, some of the other comments suggest that NLL is actually a bug fix. If so, then obviously a performance regression is better than a buggy borrow checker.
Me = Rust user who'd rather have slightly faster compile times.
But it's moot anyway given that this is a bug fix.
Look for the ones that are also tagged "unsound" for the most serious instances.
Although I still hope they will eventually come around to fix it.
And since competition is a good thing, Kotlin/Native with Gradle/Maven does allow for binary dependencies, with Swift 5.0 hopefully having them stabilized as well.
After programming C++ for years, I much prefer relying on a lowest common denominator like C for a stable ABI. This also lets Rust do whatever optimizations it wants (like compacting enums / structs).
cargo clean -p <project-name>
to only clean the current project, and leave the dependencies.[1]: http://smallcultfollowing.com/babysteps/blog/2018/10/31/mir-...
For newcomers it's the opposite. They'll have tiny projects, maybe in the hundreds-of-lines range, probably not a ton of fancy types and generics either. Their compile times are going to be quite small. But lifetimes, those will cost them a ton.
I'm not sure. Here's what I know: Rust has a complex type system. It also compiles whole packages (crates) at a time, and heavily uses monomorphization for generic code (that is probably less important for the type checking step but is quite heavy on the final compile-with-LLVM/link step).
If it was necessary, I'm sure it'd be possible to trade in some memory usage for CPU time (IIRC, a lot of memory is used for caching/memoization). But aside from outliners like that "10GB less memory usage" fix, rustc doesn't use that much RAM.
I'm guessing you could have a very efficient implementation of Rust, but that it would be rewrite instead of an incremental fix. This statement is likely to be unpopular.
Heavily templated C++ code is notoriously slow to compile so it's no surprise that heavily genericized Rust code doesn't fare much better.
On top of that Rust builds one crate at a time basically, it's not like C where you typically have plenty of smaller compilation units that are black-boxes to one-another. C++ has the same compilation model but as mentioned above templates need to be resolved completely at compile-time so all templated code ends up in headers and therefore in copy/pasted by the processor every compilation unit it's used in.
And then after all that in Rust you have the borrow checker which probably ads a non-negligible runtime overhead.
I agree that it's an interesting evolution as far as programming languages are concerned. I consider that Rust is overall superior to C but at the same time in the seventies nobody could have run a Rust compiler on anything more than a Hello World. Meanwhile I've just compiled (with optims) an auto-generated C file of about 20k lines almost instantly on my 2018 computer.
Still slower than compiling Ada, Delphi, Eiffel, D code, though.
Ultimately I think the answer would be a mix of "you could absolutely write a version of Rust that would compile reasonably on hardware from 1990", but also that the hardware limitations of that era would absolutely feed back into the design of the language in determining what features and implementation details would be feasible, which suggests that the language itself could very well look very different than it does today, even while maintaining the philosophy of no-GC memory-safety.
Your work is very much appreciated, thank you.
Thankfully someone in IRC told me back then to just switch on the feature flag. Never looked back.
Rust is still solidly in the "minutes" bucket for compile time. I don't see it ever breaking into the "seconds" bucket. That's fine. I'll take ergonomics improvements every time.
Likewise, which traits would you expect an anonymous struct to implement?
However, if you did want optional parameters, you could potentially have them be `Option` types.
I write a lot of C#, and find I only used named parameters when a function has default parameters. Even then I prefer to avoid writing such functions myself, and Microsoft seems to agree [1].
[1]: https://docs.microsoft.com/en-us/visualstudio/code-quality/c...
The point of named parameters is not having to remember the order parameters come in, especially in cases where the parameters are of the same type.
Also, IDE's aren't the only places where you read code.
You just made me feel glad I'm mainly a C dev these days. Since you typically rebuild one C file at a time (unless you make changes to header files potentially) compilation times of a fraction of a second are the norm here.
That being said I agree with you that between 1mn compile time and 1mn10s compile time it might not make all that much of a difference in practice.
However one thing Rust does that mitigates these problems a little is that diagnostics and errors are typically generated early in the build process, so generally if you see that it started off well you can focus your attention elsewhere (or even interrupt the build early to start a new one if you make small, incremental changes).
In this context I assume that the NLL checker added time to this early phase of the boot process and it might be worth optimizing that bit in particular. If you have a lifetime issue you don't want to wait 30s to have your build error.
You can also run the compiler with `cargo check` and it only does type- and borrow checking. I haven't worked on any huge Rust codebases, but in my experience it usually takes only a fraction of a second at best or a few seconds at worst.
Go does.
Indexing arrays is definitely faster. Bitsets also do array indexing but have to target particular bits too.
If I want to index 1309402 bit, I have to calculate where it is in memory, for an array representing a bit, it's super quick.
Rarely have I encountered that memory occupied by an array is a bottleneck.
NLL itself definitely worth it, no disagreement there.