Rust has done it once and, while it does work, it created quite a lot of mental overhead for new people and old until everyone more or less figured out what the hell was actually going on and a lot of docs and tutorials got updated. And that was with mostly quite minor changes that really didn’t affect the language itself, just the module system.
It’s an interesting proposal but don’t oversell it.
Rust has done it once and, while it does work, it created quite a lot of mental overhead for new people and old until everyone more or less figured out what the hell was actually going on and a lot of docs and tutorials got updated.
And it hasn’t solved all problems yet. Rust 2018 was introduced with Rust 1.31.0. We use Rust 1.31.0 (besides the latest stable) in CI, mostly to check that stuff builds on distributions with older Rust compiler versions (e.g. NixOS 19.03). However, builds break every now and then, because some dependency in the transitive closure uses edition = 2018, but also use post-1.31 features (e.g. TryFrom). It would have been much nicer if edition = 2018 actually meant Rust 2018 and no newer features.
Dependencies using too-new features is an orthogonal problem (existed before editions), and it can’t be fixed with editions. Even if editions hid new features, any dependency could still depend a newer edition than yours (e.g. request edition = 2020).
This is solvable with dependencies setting minimum required Rust version, and dependency resolution taking that into account. Completely independent of editions.
Dependencies using too-new features is an orthogonal problem (existed before editions), and it can’t be fixed with editions.
I disagree. People often use new features accidentally, because they are on newer compiler versions and just consult the rustdoc and other online documentation without looking at the minimum version annotations. If edition = xyz would strictly enforce that Rust version xyz and the standard library version corresponding to the edition, such accidents do not happen.
Such functionality could be included in the epoch system, but instead it is included in the already existing compiler version system. Basically all high quality crates specify their minimum required version, and CI test against that version. It probably would be good to include a field in Cargo.toml, but enforcement would require merging rustup into cargo, which has not yet happened.
Textual inclusion and build system fragmentation makes this hard for C++.
Rewriting C++ files semantically correctly is PITA. It’s hard to get an AST in the first place, and then losslessly going from the new AST back to source form before C preprocessing is a surgery with lots of tricky edge cases.
cargo fix had much easier job, because it could leverage the compiler (there’s one true build system), and apply compiler-suggested changes directly (since the code seen by the compiler is the actual source code).
In C++ you’d have to be careful to prevent header files from changing their edition when they’re included somewhere. It’s not going to have a concept or packages like Cargo, so you’d probably have to rely on manually added inclusion guards such as extern "c++22-edition" { inside the headers, and extern "default-edition" { #include <old_stuff.h> } outside. Forgetting them would lead to fun compilation errors.
The author says that C++ modules are a prerequisite for this idea, so they’re really only proposing that this be done after it becomes possible to have imports without textual inclusion.
Linking compatibility only though? You can link a crate which uses the old epoch against a crate built which uses the new epoch and vice versa, even though you can’t necessarily freely copy paste code between them.
My perspective when I see this proposals for major changes (in different languages) is “This language has been alive for 20-40 years. How does that affect your decision?”
That doesn’t directly argue for this particular proposal of any other. In fact, I think it sometimes helps justify waiting a few years to figure out a good strategy for making difficult transitions easier (and avoiding misguided new features).
Still, I think it pushes for accepting one-time costs for the sake of a language’s long term health.
Personally, if we’re going to be breaking, wrapping, or rewriting any old code at all (and one way or another, we will have to), I’m happier just going whole hog and moving on to a C++-like but better language: D.
D for me is the C++ I always wanted: expressive, safe, familiar. I can write D like I used to write C++ and I was almost immediately able to get stuff done without having too learn too much of anything new.
“Also, someone already did it. And it works.”
Rust has done it once and, while it does work, it created quite a lot of mental overhead for new people and old until everyone more or less figured out what the hell was actually going on and a lot of docs and tutorials got updated. And that was with mostly quite minor changes that really didn’t affect the language itself, just the module system.
It’s an interesting proposal but don’t oversell it.
Rust has done it once and, while it does work, it created quite a lot of mental overhead for new people and old until everyone more or less figured out what the hell was actually going on and a lot of docs and tutorials got updated.
And it hasn’t solved all problems yet. Rust 2018 was introduced with Rust 1.31.0. We use Rust 1.31.0 (besides the latest stable) in CI, mostly to check that stuff builds on distributions with older Rust compiler versions (e.g. NixOS 19.03). However, builds break every now and then, because some dependency in the transitive closure uses
edition = 2018
, but also use post-1.31 features (e.g.TryFrom
). It would have been much nicer ifedition = 2018
actually meant Rust 2018 and no newer features.Dependencies using too-new features is an orthogonal problem (existed before editions), and it can’t be fixed with editions. Even if editions hid new features, any dependency could still depend a newer edition than yours (e.g. request
edition = 2020
).This is solvable with dependencies setting minimum required Rust version, and dependency resolution taking that into account. Completely independent of editions.
Dependencies using too-new features is an orthogonal problem (existed before editions), and it can’t be fixed with editions.
I disagree. People often use new features accidentally, because they are on newer compiler versions and just consult the rustdoc and other online documentation without looking at the minimum version annotations. If
edition = xyz
would strictly enforce that Rust versionxyz
and the standard library version corresponding to the edition, such accidents do not happen.Such functionality could be included in the epoch system, but instead it is included in the already existing compiler version system. Basically all high quality crates specify their minimum required version, and CI test against that version. It probably would be good to include a field in Cargo.toml, but enforcement would require merging rustup into cargo, which has not yet happened.
Textual inclusion and build system fragmentation makes this hard for C++.
Rewriting C++ files semantically correctly is PITA. It’s hard to get an AST in the first place, and then losslessly going from the new AST back to source form before C preprocessing is a surgery with lots of tricky edge cases.
cargo fix
had much easier job, because it could leverage the compiler (there’s one true build system), and apply compiler-suggested changes directly (since the code seen by the compiler is the actual source code).In C++ you’d have to be careful to prevent header files from changing their edition when they’re included somewhere. It’s not going to have a concept or packages like Cargo, so you’d probably have to rely on manually added inclusion guards such as
extern "c++22-edition" {
inside the headers, andextern "default-edition" { #include <old_stuff.h> }
outside. Forgetting them would lead to fun compilation errors.The author says that C++ modules are a prerequisite for this idea, so they’re really only proposing that this be done after it becomes possible to have imports without textual inclusion.
I think this contradicts the example he provided for Rust, where dyn is required in a newer version.
dyn is not required in 2018: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=79d725904b0ec7d43602eed28da57d13
Still, not all 2015 code works in 2018, the trick is that you can compile mixed codebases.
Linking compatibility only though? You can link a crate which uses the old epoch against a crate built which uses the new epoch and vice versa, even though you can’t necessarily freely copy paste code between them.
My perspective when I see this proposals for major changes (in different languages) is “This language has been alive for 20-40 years. How does that affect your decision?”
That doesn’t directly argue for this particular proposal of any other. In fact, I think it sometimes helps justify waiting a few years to figure out a good strategy for making difficult transitions easier (and avoiding misguided new features).
Still, I think it pushes for accepting one-time costs for the sake of a language’s long term health.
Personally, if we’re going to be breaking, wrapping, or rewriting any old code at all (and one way or another, we will have to), I’m happier just going whole hog and moving on to a C++-like but better language: D.
D for me is the C++ I always wanted: expressive, safe, familiar. I can write D like I used to write C++ and I was almost immediately able to get stuff done without having too learn too much of anything new.