Hacker News new | ask | show | jobs
by phire 520 days ago
No. The point of rust editions is that they do break support for older code, which is very different to what go has now settled on.

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.

1 comments

The trouble with the Rust community is that it is terrible at communication. That may be why you extend a presupposition that everyone understands the meaningful difference between Rust editions and Go version directives, but I can't tell a difference beyond the frivolous like syntax used. Based on the documentation of each they seem like the exact same concept, with the exact same goals in mind. As a result, unfortunately, your point is not yet made. Perhaps you can break the cycle and describe for every day people how Rust editions are fundamentally different?
Editions allow making breaking changes to Rust without splitting the ecosystem - no hassle caused to existing code unless it opts into the new edition and its breaking changes. There's currently editions 2015, 2018, 2021, and 2024. When a new edition is introduced, it can make breaking changes such as introducing new keywords, but every previous edition remains supported forever by newer compiler versions.

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.

> The key part is that editions are configured per-library - libraries A and B might use editions 2015 and 2021

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?

The documentation is probably the best resource to start with the concept and how it works / what the goals are: https://doc.rust-lang.org/edition-guide/editions/index.html

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

As stated before, the documentation in both languages was already consulted. It did not clear up how Rust is any different than Go in this regard. Consider a simple example from the Go documentation: Support for numeric underscores, which was not a part of the original language and later included in a 'new edition' of Go.

   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.

Sorry to have disappointed you. I don't walk, talk, or sleep either Rust or Go, but was trying to provide some resources to help in case you hadn't seen them yet.

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

[1] https://go.dev/doc/modules/gomod-ref#go

Thanks for trying. But it is the "which is very different to what go has now settled on" that we are trying to get to the bottom of. It appears from your angle that you also conclude that Go has settled on the very same thing, frivolous implementation details aside. Hopefully phire will still return to dumb it down for us.
Ok... the only thing that go version directives do is selectively enable new features. Essentially, they are only really there to help you ensure your code will continue compiling in older versions of the compiler. Hell, until recently it wouldn't even throw an error if you tried to compile code with a future version directive.

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.