Just look at how rust does it. Rust 1.0 code still works in the latest version of rustc, you just need to set the project to the Rust 2015 edition. You can even mix-and-match editions, as each crate can have a different edition. Newer versions of rustc will always support all previous editions, and breaking changes are only ever introduced when a new edition is released every 3 years. If the crate is stable, no real reason to upgrade, it will work forever. And if you do need to update a project, you can split it into multiple crates and do it incrementally.
Just imagine how much smoother the python 3 transition would have been if you could transition projects incrementally, module by module as needed.
Just as you say, Python could have introduced what is found in Python 3 without breaking Python 2 support. Which is the direction Go has settled on; hence why Go 2 is off the table. Go 1.0 and Go 1.23 are very different languages, but backwards version support is retained, so no need for a new major version.
IMO, it's the best of both worlds. Old code continues to work forever, but your language design isn't held back by older design mistakes.
For example, https://doc.rust-lang.org/edition-guide/rust-2021/warnings-p... - code that produced a lint warning in the 2018 edition produces a compiler error in the 2021 edition. That would be something that can't be done in a backwards compatible way without editions
Another example would be changes to the import syntax https://doc.rust-lang.org/edition-guide/rust-2018/path-chang... - the compiler will forever support the 2015 behavior in crates that use the 2015 edition, but crates using newer editions can use the newer behavior
i := 10_000_000
Using the 1.13 or later version of the gc compiler, if your go.mod specifies anything after 1.12 the above compiles fine. But if go.mod asserts go 1.12 or earlier, you will get a compiler error from the above code as the compiler reverts to 1.12 or earlier behaviour based on the version directive. That sounds exactly like what you described! And, like I said before, Rust's documentation too echoes to my read that editions accomplish basically the same thing and exist for the same reason Go version directives exist.But the earlier commenter indicated that they are very different. So, unfortunately, you have again failed to break the cycle. We need something dumbed down for us regular people, not something directed at those who walk, talk, and sleep Rust.
One difference I noticed in the docs is in the Go Reference it says the "go" line of the "go.mod" has to be greater than or equal to the go line of all that modules dependencies, if the go line is 1.21 or higher, so a module for 1.21 can't depend on a module for 1.22 [1]
That restriction doesn't apply for Rust, a library using the 2015 edition can use a dependency that uses the 2018 edition, for example.
That's just one difference I noticed in the implementation. The goals seem very similar if not the same
The actual backwards compatibility in go is achieved by never removing functionality or syntax. New versions can only ever add new features/syntax. If there was a broken function in the API, that function needs to stick around forever, and they will be forced to add a second version of that function that now does the correct thing.
So, you can take code written for go 1.0, slap on a "go 1.23" directive and it will compile just fine. That's the guarantee that go provides. Well, mostly. There are a few examples of go 1.0 code that doesn't compile anymore, even when you use a "go 1.0" directive.
But not being able to remove anything ever is limiting.
A good example of how this can be limiting is reserved keywords. Go has a fixed set of reserved keywords that they picked for 1.0, and they can never reserve any more, any code using them as identifiers will break. Any new feature needs to be carefully designed to never need a new reserved keyword. Either they reuse an existing keyword (which c++ does all the time), or they use symbols instead.
But rust can reserve new keywords. The 2018 edition of rust reserved "async" and "await" for future async functionality and "try" for a potential try block. Rust did reserve "yield" from the start for generators, but decided they needed a way to mark a function as a generator, so in the 2024 edition, "gen" is now a reserved keyword, breaking any code that uses gen as a function/variable name.
Do note that rust also follows the go strategy within an edition. There is only one new edition every three years, and it would be a pain if all new features had to wait for a new edition. So the async/await keywords were reserved in the 2018 edition, but didn't actually get used until the end of 2019.
This means that just because your rust version supports the 2018 edition, doesn't mean it will compile all 2018 code. The editions are just for breaking changes, and there is a seperate minimum "rust-version" field that's somewhat equivalent to go's "go 1.x" directive. Though, "rust-version" doesn't disable features, it's just there to provide a nice clean warning to users on old compilers. Ideally in the future it will gain the ability to selectively disable language features (as rust already has extensive support for selectively enabling experimental language features in nightly builds, which we haven't even talked about here).
Basically, rust editions allow all breaking changes to be bundled up and applied once every three years. They also provides a way for compilers to continue supporting all previous editions, so old code will continue to work by picking an old version. While go's version directive looks superficially similar to editions, it is there for a different reason and doesn't actually allow for breaking changes.
The previous version removed functionality related to loop variables, so that is not strictly true. You might be right that the project doesn't take change lightly. There has to be a very compelling reason to justify such change, and why would it be any other way? If something isn't great, but still gets the job done, there is no reason to burden developers with having to learn a new language.
Go is not exactly the most in-depth language ever conceived. There is not much functionality or syntax that could be removed without leaving it inoperable. But there is no technical reason why it couldn't. The mechanics to allow it are already there, and it doesn't even violate the Go 1 guarantee to do so under the operation of those mechanics.
So, sure, it is fair to say that there is a social reason for Go making as few breaking/incompatible changes as is possible, but we were talking about the technology around allowing breaking/incompatible changes to co-exist and how the same concept could have been applied to Python. In Rust community fashion, I am not sure you have have improved on the horrible communication. We recognized that there is some difference in implementation details right from the onset, but the overall concept still seems to be the same in both cases to me.
Again, we need it dumbed down for the every day average person. Your audience doesn't eat monads for breakfast like your expression seems to believe.
Ah, interesting. I searched but I couldn't find any example of go actually making a breaking change. Rust has a massive document [1] documenting every single breaking in single document. With go you kind of have to dig through the release notes of each version.
So, maybe Golang are relaxing their stance slightly on backwards compatibility, now that they have a mechanism that does kind of work. Which is good, I encurage that. But their offical stance is still that most code from go 1.0 should work without issues.
> there is no reason to burden developers with having to learn a new language.
To be clear, many of the breaking changes in Rust editions are the same kind of thing as that go loop example. Edge cases where it's kind of obvious that should have always worked that way, but it didn't.
The average programmer will barely notice the changes between editions, they won't have to re-learn anything. The major changes to the language usually come in regular feature releases; They are optional additions, just like go.
While the edition mechanism could be used to make changes as big as Python 3, so far it hasn't and I don't think it ever will. Decent chance there will never be a "rust 2" either.
> In Rust community fashion, I am not sure you have have improved on communicating the difference
Sigh... you aren't wrong:
----------
The difference is more attitude than anything else.
Go strives to never have breaking changes, even if they are forced to bend that rule sometimes. They do have a mechanism that allows breaking changes, but seems to be a reasonably recent innovation (before go 1.21, it wouldn't even error when encountering a future go version directive, and they didn't use it for a breaking change until 1.22)
Rust accepts that it needs to do breaking changes sometimes, and has developed mechanisms to explicitly allow it, and make it as smooth as possible. And this mechanism works very well.
----------
BTW, I'm not even saying go's stance is wrong. Rust needs this mechanism because it's a very ambitious language. It never would have reached a stable 1.0 unless it recognised the need for breaking changes.
Go is a much simpler language, with a different target market and probably should be aiming to minimise the need for breaking changes.
My original point is that "never make breaking changes" is the wrong lesson to take away from Python 3. And that Rust's editions provide a very good example of how to do breaking changes correctly.
Here we go again, but the point of editions, as far as I can tell, is so that there are no breaking changes. A value Go also holds. As a result, both projects are still at version 1 and will likely always forever be at version 1.
So, if we round back to the start of our discussion, if Python 2 had taken the same stance, there would never be a Python 3. What we know of as Python 3 today would just be another Python 2 point release. Which is what the earlier commenter that started all this was saying – Go will not move to Go 2, and Rust won't move to Rust 2, because nobody wants to make the same mistake Python did.
I understand you have an advertising quota to fill, but introducing Rust into the discussion was conversationally pointless.
This is a ridiculous statement and you are intentionally engaging in bad faith. There's no need for this in response to people genuinely trying to answer your questions. Be better
The key part is that editions are configured per-library - libraries A and B might use editions 2015 and 2021, and your application could use edition 2018 and depend on those libraries, and it works.
If you wrote a library with the original 2015 edition, and never upgraded it to deal with new `async` and `await` keywords added in the 2018 edition, that's totally fine. Newer compilers will continue to compile it in its configured edition=2015 mode, where the new keywords don't exist (so your local variable named `async` still compiles), and new code written against newer editions may still use this 2015 edition library with no issue.
Editions are different from Go version directives because you use them to say "my library needs features added in this Go version", but they don't enable Go to make breaking changes to the language.
Editions can't do every kind of breaking change however - they mostly work for syntax level changes, and don't work for things like tearing out regrettable parts of the standard library.
In what way is that key? It still reads as being the same as the Go version directive. Obviously there are some differences in the implementation. For example, Go puts it in go.mod, while Rust puts it in Cargo.toml, but at a conceptual level I fail to see any fundamental difference. As you describe it, and how the documentation describes it, they attempt to accomplish the same thing for the same reason.
But, as phire puts it, they are "very different". But I don't see how. The carrying on of the tradition of the Rust community being horrible at communication carries on, I'm afraid. As before, you are going to have to speak to those who aren't deep in the depths of programming languages. Dumb it down for the reader who uses PHP and who has never touched Go or Rust in their life.
> they don't enable Go to make breaking changes to the language.
What, exactly, do you mean? The change to loop variable semantics comes to mind that was clearly a breaking change to the language, but gracefully handled with the version directive. What purpose are you under the impression the directive serves if not for dealing with breaking changes?
> Just imagine how much smoother the python 3 transition would have been if you could transition projects incrementally, module by module as needed.
That would require manually converting strings to the correct type at each module boundary (you can't do it automatically, because on the Python 2.x side, you don't know whether or not a string has already been decoded/encoded into a specific character encoding; that is, you don't know whether a Python 2.x string should be represented by a "bytes" or a "str" on the Python 3.x side). That's made even harder by Python's dynamic typing (you can't statically look at the code and point all the places which might need manual review).
Go 2.0 was basically a blank check for the future that said "We may need to break backwards compatibility in a big way". It turns out the Go team does not see the need to cash that check and there is no anticipated upcoming feature in the next several years that would require it.
The last one that I was sort of wondering about was the standard library, but the introduction of math/rand/v2 has made it clear the devs are comfortable ramping standard library packages without a Go 2. There are a number of standard libraries that I think could stand to take a v2; there aren't any that are so broken that it's worth a v2 to hard-remove them. (Except arguably syscall [1], which turns out it doesn't belong in the standard library because it can't maintain the standard library backwards compatibility and should have been in the extended standard library from the beginning, but that's been the way it is now for a long time and also doesn't rate a v2.)
(And again let me underline I'm not saying all the standard library is perfect. There is some brokenness here and there, for various definitions of "brokenness". I'm just saying it's not so broken that it's worth a v2 hard break at the language level and hard elimination of the libraries such that old code is forcibly broken and forced to update to continue on.)