Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Having the new borrow checker finally on in all cases is nifty. For valid programs the behavior is the same as the previous version, but in the event of a compiler error, previous versions would still show you the output of the old borrow checker. I've already had one case where someone came asking about a confusing borrow checker error message and I just had to suggest that they try compiling with 1.63 and the new error message was clear enough that it answered their question immediately.

Now we just need to do it all over again once Polonius finally arrives. :P



Migrations aren't ideal, but I'm excited to see continued innovation in borrow-checking. This is a major area where Rust is exploring truly new territory as a language, so growing pains are to be expected, and it's encouraging to me that Rust's semantics allow borrow checking to keep getting better without breaking changes


> it's encouraging to me that Rust's semantics allow borrow checking to keep getting better without breaking changes

Just to be clear, these are breaking changes. If the new borrow checker didn’t break existing code, they would have removed the old one long ago. They’ve just decided that the observable effects of the breaking change in the ecosystem are small enough (especially after warning about them for years) that they are okay making the breaking change without a semver-incrementing Rust 2.0.

To be fair, the reason for the breaking changes is generally to fix soundness issues (of course, not every example of code now banned will exhibit unsoundness), but that doesn’t cover all of them. There were some completely sound (albeit useless) examples of partial initialization that broke with NLL:

https://github.com/rust-lang/rust/issues/21232#issuecomment-...

https://github.com/rust-lang/rust/issues/54987

Strict adherence to semver would require still accepting this code.


Sure, I guess all I meant was that the fundamental semantics of borrowing and lifetimes that were chosen at the beginning have continued to scale up to a better and better borrow-checker experience. They haven't found deep limitations of the language that prevented innovation without going back to the drawing board.


I'm probably mixing different things up, but I thought the non-lexical lifetime changes made the borrow checker more permissive in the sense that some code that the compiler would reject using the lexical borrow checker, would be allowed using the non-lexical borrow checker.

If this is so, how are they maintaining edition compatibility? It seems like you could write code for the 2015 edition that would build with the new compiler, but not an older (2015 edition compliant) compiler.


The new borrow checker both allows more to compile (by being more precise about tracking control flow, allowing it to prove more advanced things about lifetime usage) and also less code to compile (again, by being more precise about tracking control flow, allowing bugs in the old borrow checker to be fixed (see https://github.com/rust-lang/rust/issues?q=is%3Aissue+label%... for examples)).

The edition compatibility story here is interesting. When the 2018 edition came out, the new borrow checker was only turned on for code on the 2018 edition. Meanwhile, in the 2015 edition, it would run both borrow checkers and yield a warning if your code passed the old borrow checker but not the new one. After a migration period, the new borrow checker was eventually made the default on the 2015 edition as well. Because of the aforementioned soundness bugs fixed by the new borrow checker, this was seen as acceptable (soundness fixes are explicitly allowed by the stability policy, although the Rust developers still try to provide migration periods to make them less painful).


Editions only guarantee that old code builds with new compilers, not vice versa. New features are added to all editions unless they require e.g. new keywords that are available only in new editions.


Ah, that is right. I apologize, I think I've learned that three times now, but for some reason my brain refuses to accept it.

I like that previous editions get new features when possible, unlike say C++, but I wish they'd gone with semantic versioning for the language version, like python did when both 2 and 3 were active. Oh well, I just need to remember that the edition is like the major version, and compiler version is sort of like minor version, and both are needed to specify forwards compatibility.


I'm not sure semver would really do it here, because then you could have say:

  2.17
  1.17
What would you call the compiler binary that could compile both language v1 and language v2, with (shared) minor-version .17?

The issue is that semantic versions are a hierarchy, whereas Rust edition x compiler version are a matrix


It might make more sense to think of editions as optional changes to the surface syntax, and nothing more. They are not nearly as important as the name makes them sound.


They are often small, but it's a mistake to say they're surface-level. Changes to syntax or semantics only get made when absolutely necessary to allow for essential features or fixes.


Editions are orthogonal to compiler versions. It is perfectly fine if code builds with a new compiler but not an old one. Another example of this: you can write code in a 2015 edition crate that calls the new `std::thread::scope` function.


Editions promise old code works with new compilers.

They do not promise new code works with old compilers




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: