Rust 1.56.0 and Rust 2021
blog.rust-lang.org
blog.rust-lang.org
[profile.profiling]
inherits = "release"
debug = true
[1]: <https://github.com/rust-lang/cargo/pull/9943>And then there's the less fireworky, but still appreciated, Iterator::map_while[2], which is going to be in Rust 1.57.
https://doc.rust-lang.org/book/appendix-07-nightly-rust.html
It's a release train, similar to that used by Chromium and Firefox.
Edition releases (e.g. Rust 2021) are reserved for breaking changes only, and to retain Rust's stability promise, are opt-in.
Is that different from compiling one lib with --std=c++11 and another with --std=c++17?
#if __STDC_VERSION__ >= 201112L
// C11 feature
#endif
#if __cplusplus >= 201103L
// C++11 feature
#endifIf you have libraries that you don't care if they're C or Pascal or a Lisp implemented in raw machine code, then this works just fine and you needn't care about Rust's editions feature. Rust will also cheerfully consume these libraries although of course everything about them is by definition Unsafe in Rust terms.
But most people want their C++ libraries to deliver a bit more than "Here is some machine code, and here are some symbol names that map to the machine code or to raw binary data". Like maybe they want to be able to implement an Interface the library describes, or they want to use a Concept the library names. You can't do those things using language-independent object files.
Rust library A, from edition 2021 can implement a Trait from library B (edition 2018) on its thin wrapper of a type from library C (edition 2015) and then you can consume the resulting type, with its trait implementation, from your Rust 2018 program.
Which isn't such a big problem if your C++ library exposes a minimal C-like API from it's headers, with most of the meat of the library hidden away in source files, but might be a very big problem if your C++ library is a miserable little pile of ~~secrets~~ templates, a la boost.
Also, this doesn't work with templates, and modern idiomatic C++ tends to be template-heavy.
PS: The other way around is more obviously broken, with the C++17 lib headers getting compiled using C++11.
This is in practice what the maintainers of the three standard libraries have to do, perhaps one or more of them will offer their opinion about that experience?
1 - Rust is still quite young and doesn't have 30 years of accumulated editions
2 - There is still only one major Rust compiler
3 - Editions are designed only to work when compiling the whole project, including 3rd party dependencies, from source code within a single build
4 - So far the editions don't have semantic breaking changes across editions, where behaviour changes across the edition border
5 - There is no plan to ever have editions work across ABIs
So "The edition system keeps old stuff working without any effort." might not be true when Rust achieves an adoption scale similar to C and C++, in about 20 years, with several accumulated editions, and a couple of compilers in use.
I might be proven wrong, but that is how I see it today.
Rust 1.0 was six years ago. When do we start the clock on six years of C++ evolving while also having "old stuff working without any effort" ?
C++ 98 to C++ 03 was five years, and that introduces almost no features at all. C++ 11 was eight years later but it's notoriously incompatible to prior versions of C++. C++ 14 is only three years, C++ 17 has breaking changes, as does C++ 20...
I think C++ 20 should have taken Epochs (yes even at considerable cost to other new features like Concepts) for this reason. I think ten years from now even if Rust has found it can't achieve everything it wants to via editions, this feature will be generally considered to be a good idea, like generics or string literals. Something you need a specific rationale for not including in your general purpose language.
I might be proven wrong too about how far this can go, but I feel like the Rust 2018 and 2021 editions already prove the value of the idea.
Lets say you have a noexcept function compiled in C++20 that calls a throws() function, compiled in C++03, which actually ends up throwing, linked together.
What is the runtime supposed to do now?
To which semantics does it follow now?
Call std::unexpected() as it is supposed to do pre-C++14 or call std::terminate() as it should do in C++20?
What about the user defined handler that was configured for such scenario? Which of them gets called, or do both get called, in which order?
Maybe I am doing it more complex that it is, but I see several scenarios from point of view where multiple compilers, binary libraries and semantic changes come into play, editions turn just into another way to define language versions, because they don't cover all possible uses cases how a language might evolve.
Anyway, history will tell how things work out in the end.
Not solving everybody's problem is not the same thing as not solving anybody's problem.
You present subtle considerations which, ignoring the fact that they assume Epochs don't exist in C++ 20 yet ask what Epochs should have done in C++ 20 - might have taken up committee time if Epochs had advanced, and which now can only be answered in the vaguest way, they should definitely have decided on a coherent strategy for resolving such problems.
It is true that for any conceivable change, Hyrum's Law applies, and so it would apply to Epochs just as it does for Rust's editions. Mara's "competition" for writing Rust that gives different results when re-formatted provides examples of the sort of stuff Hyrum's Law invariably breaks. Nobody should be writing non-toy programs like that and in Rust it seems like nobody is. It's a sad fact that too often C++ programs are written in a very fragile way and many of them can and do break with the least provocation.
You can't magically rewrite all those programs, but you can make fragile techniques like SFINAE unattractive to propagate into new programs, and I argue Epochs would have allowed C++ to begin the much harder part of Stroustrup's ever-evolving quest to ship a good programming language - not adding yet more kitchen sinks but removing parts of the language that in hindsight were a bad idea and revisiting old design decisions in the light of what has been learned.
For example, the Epoch semantics in xlCC would have to behave as in VC++, to avoid too many nasty surprises in cross platform code that is in production for decades.
We're not talking about 1998 here. This is in 2017. By this point plenty of people have experience already using languages with Optional types that do what you actually want here, but the C++ committee decided no, C++ is the footgun language, it's what we're known for.
But OK, in an alternate universe where the committee doesn't introduce footguns on purpose to prove their Real Programmer credibility, and where we do get Epochs, what should happen for fraught situations so as to deliver consistency?
The committee should decide on a rule. I know that's often portrayed as too difficult for such problems, but it won't get easier in subsequent versions. That's why I think it would have made sense to delay long awaited work like Concepts if that was the only way to land Epochs. Concepts is already too late for the main act and being a little later barely makes a difference. Whereas Epochs gets harder to do every version, so the sooner the better.
Regardless, at the very least, you would need to write the headers to be interoperable.
Because editions are opt-in (and library-level source metadata) the language itself can be modified in non-backwards-compatible ways.
So for instance a C++ with editions could make ctors `explicit` by default, or it could entirely change the automatic member generation (by removing it for instance). As long as the ABI and API remain compatible, that's fine.
Anybody who literally refers to std::iter::Iterator gets the old ones of course as does any library code from prior editions, but the documentation could lead those few people in the right direction. And presumably std::better::Iterator politely implements std::iter::IntoIterator because why not.
I would be interested to understand if they're allowed to replace the macros. The standard macros aren't actually from the prelude, but instead if you aren't no_std you get all the standard macros anyway. Are they allowed to change those in a future edition? Or not?
You can kinda do this now with `#[no_implicit_prelude]` I think but it has somewhat odd semantics. It applies to all submodules, unlike most attributes, and then if you define your own prelude you need to use it in every submodule because they won't all have their own.
If it's gonna have global effect I think it'd be better if it was:
#[prelude]
mod my_prelude {
use std::whatever::*;
}
and then my_prelude would be included in all submodules by default.Meanwhile, the thing that is in the language is.. weirder and arguably worse.
Also, it was closed in 2015; it was a different time back then. I don't think that RFCs from 2015 should be kept open just because. Arguably, Rust shouldn't have 88 RFCs open (as of now), either.
Maybe that would be considered too confusing though.
Rust editions are closer to source code parsing modes. More like enabling trigraphs in C or "use strict" in JS.
Additionally, textual header inclusion in C makes mixing versions tricky. Rust has properly isolated crates, and tracks edition per AST node (so that even cross-crate macros work correctly with mixed editions).
By the way the difference is that Rust is not a standard, thus is easier to evolve (the process is much shorter). On the other side, the fact that a language changes slowly it's something good in a way, it means that you don't have to continue to change the way you do things, and update older projects.
That to everyone that has to maintain code for decades it's important. And every serious software project (not hobby stuff) does stay in production decades really. I don't use Rust, or even C++, for that reason.
Isn’t the point of Rust editions that this is also true for Rust? Don’t want to update to a new edition? Then… don’t. The old ones are maintained.
Another thing is ABI.
C and C++ are ABI-stable, which means that many historic mistakes (intmax_t, std::regex, polymorphic allocators) are impossible to fix.
Rust only promises source compatibility, not ABI compatibility, so it has a lot more freedom to tweak its design.
https://thephd.dev/binary-banshees-digital-demons-abi-c-c++-...
As an example, here's a comment of mine on the original RFC (which was called "epochs"): https://github.com/rust-lang/rfcs/pull/2052#issuecomment-315... (the part with "Some language development comparisons")
Maybe it’s ironic that semver (machine-readable versioning) ended up being a liability due to interpretation by people (i.e. we can’t release a 2.0 because it would “send the wrong message”).
I think I'm in agreement :)
> All observable behaviors of your system will be depended on by somebody.
Even minor bugfixes will at some point invariably break somebody who depended upon the broken behavior. So semver really boils down to a human assessment of whether any potential breakage is incidental or intended.
As a library author I don't feel there's any value in considering Hyrum's Law. I can't help those poor fools, for all I know they're relying on me not updating the documentation to warn them they shouldn't rely on undocumented behaviour I'm about to change... Rust provides a pretty clear line in the sand on API changes we can use to choose semver policy. If your program has a proc macro to copy-paste sections of my source code into yours so you can access my non-public functions, I can't see that from where I am and you're lucky it ever worked, I am under no obligation to ensure it magically stays working in my next bugfix release.
That's necessary b/c there are lots of breaking changes between language/compiler versions[3].
--
[1] they are more like DB triggers than contracts
[2] https://docs.soliditylang.org/en/develop/layout-of-source-fi...
[3] https://docs.soliditylang.org/en/develop/050-breaking-change...
> It just instructs the compiler to check whether its version matches the one required by the pragma. If it does not match, the compiler issues an error.
This is very different from Rust where your Rust 1.56.0 compiler will cheerfully compile Rust 2015, Rust 2018 and Rust 2021 code, into the same program even. Rust editions are not about the compiler version, they're about the language and every Rust compiler will compile every language edition it knows about.
Perl can do it not only at module level, but at block level within a single source file.
Adding 20 lines of #[!feature(...)] to every project would get old quick.
Thanks to all the contributors for getting us to the 2021 edition!
See for example the summary at the top of https://rust-lang.github.io/rfcs/2052-epochs.html
It seems to to me that that aspect has now been dropped: TFA simply says "Editions are a mechanism for opt-in changes that may otherwise pose backwards compatibility risk."
I'm not sure this change of direction has been officially announced anywhere, though.
(I think this is a good change: last time I saw cases of people asking questions like "How do I do foo in Rust 2018", and getting a mix of answers like "Nothing in Rust 2018 affects foo" and "Since Rust 1.20 you've been able to use std::foo::bar to do that".)
When IntoIterator for arrays first stabilized (with the hack hiding into_iter() for backwards compatibility) I considered making all the documentation changes to use natural arrays in standard library examples, which of course would now be the obvious way to write it whereas previously it was ugly.
I didn't do it (and now I have a job keeping me too busy) and I haven't gone back to look at examples to see if all/ most / some were updated to use arrays in the now natural way.
• lumping marketing of cool features with an announcement of a few incompatible changes was easily misinterpreted as all new features requiring a new incompatible edition (while in fact almost all marketed features were already available in the old edition).
• celebration of features developed in recent years under one big event sounded like all these features were brand new and released at once.
For people who didn't follow Rust development, the announcement sounded like Rust suddenly made a lot of incompatible changes.
I came across this post which presents an example using rust's transmute and it's incorrect behavior when the alignment is different. https://andrewkelley.me/post/unsafe-zig-safer-than-unsafe-ru...
Is this issue still present? I tried to figure it out the other day using latest rust's nightly but the llvm ir output has so much going on that I didn't _really_ understand what was going on
> Because transmute is a by-value operation, alignment of the transmuted values themselves is not a concern. As with any other function, the compiler already ensures both T and U are properly aligned. However, when transmuting values that point elsewhere (such as pointers, references, boxes…), the caller has to ensure proper alignment of the pointed-to values.
The post you've linked transmutes a reference, and as the caller fails to explicitly ensure proper alignment, it explicitly risks invoking undefined behavior - I would consider the code buggy. The easiest way to prove it's broken would be to create an reference that's more likely to be unaligned (&array[1] instead of &array[0]?) and run the code on a less misalignment-tolerant platform (ARM?).
Here are some 100% sound alternatives using bytemuck (no unsafe required!) and core::ptr::{read,write}_unaligned (unsafe required):
https://play.rust-lang.org/?version=stable&mode=debug&editio...
> Because transmute is a by-value operation, alignment of the transmuted values themselves is not a concern. As with any other function, the compiler already ensures both T and U are properly aligned. However, when transmuting values that point elsewhere (such as pointers, references, boxes…), the caller has to ensure proper alignment of the pointed-to values.
This code is doing the latter incorrectly, and therefore invokes UB, as far as I can tell. To be honest, I don't use transmute very often and so I don't have every single last corner case about it memorized.
It's not so much an "issue" as it is "Here's an API that's extremely sharp in Rust, and a similar, but less sharp API in Zig."
For this reason, we never reach the question of alignment because we know neither i32, u8, nor any other type has the same layout as Foo (undefined layout).
It is certainly true that unsafe Rust is veeeeery unsafe, as perhaps evidenced by me being the first person to point out the repr issue. On the other hand this scheme has a lot of advantages for writing safe Rust.
#[repr(u16)]
enum Foo { A = 0, B }
#[repr(u16)]
enum Bar { A = 3200, B }
struct FooOrBar(u16);
And then proceeding to violate all of the unwritten rules of Rust by wrangling casts across these types like a goddamn wizardI'm not proud of this...
I still feel like this trick has it's place though, as an example take a look at where I stole this trick from, by matklad [0] and tell me what you think :)
[0] https://github.com/rust-analyzer/rowan/blob/d2c7843858da9d9e...
If you have a byte array that you've received over a network connection that represents an array of floats, or you need to convert a 32bit RGBA pixel buffer that you got from some clang ffi binding to a byte pixel buffer without having to split/copy to a new vector.
See https://blog.rust-lang.org/2021/06/17/Rust-1.53.0.html#whats...
"Since this special case for .into_iter() is only required to avoid breaking existing code, it is removed in the new edition, Rust 2021, which will be released later this year."
As a user, you're right that there's not really an external-facing change here.
> Until Rust 1.53, only references to arrays implement IntoIterator. This means you can iterate over &[1, 2, 3] and &mut [1, 2, 3], but not over [1, 2, 3] directly.
...now, you can also iterate over [1, 2, 3] etc.
https://doc.rust-lang.org/edition-guide/rust-2021/IntoIterat...
"for x in myArray" works fine, just like "for x in myVector" but whereas "myVector.into_iter().foo()" does what you expect, "myArray.into_iter().foo() is actually giving foo an iterator over the references just as it would have in 2017 and now produces a warning about this into the bargain.
In Rust 2021 myArray.into_iter() does what a modern Rust programmer expects it to do, provide an iterator over myArray itself.
The warning does explain how you can get that iterator in 1.53.0 of course, but you need to write some ugly syntax whereas in Rust 2021 the obvious syntax just does what you expect as if arrays had always been IntoIterator.
[1]: https://twitter.com/ryan_levick/status/1443202538099073027
Rust has had LTO for quite a while, and it's normally a source of longer compilation times rather than shorter ones (since LTO in LLVM-world involves mashing all of the bitcode together and (re-)running a lot of expensive analyses to further optimize across translation unit boundaries.
OTOH they've been making continuous improvements to the incremental compilation mode since 1.51/2, so that's probably among the sources of improvements here.
https://perf.rust-lang.org/compare.html?start=ef9549b6c0efb7...
Some things compile decently faster (~ 10%), some things compile a little faster or slower (~ +/- 3%), some things have a bigger perf hit but not as many as had a bigger perf gain (~ -10%).
So it's faster on average but the data is muddy enough that you probably wouldn't stick it front and center on your release notes.
It's less of a universal improvement than the pass manager, but for most of the benchmarks that it does impact it seems just as large, or larger.
https://github.com/rust-lang/rust/commit/63cc2bb
> Enable new pass manager with LLVM 13
Maybe it was enabling PGO rather than any code change, I've heard it mentioned that happened recently.
There have been a few RFC trying to work on deriving a type from another, and while I agree it's much more complex than it sounds, I also find it's a huge missing point.
If I have to reimplement a wrapper myself for all Traits, I most likely won't bother, leading to less typesafety, leading to more bugs :(