Back

Rust 1.56.0 and Rust 2021

389 points2 yearsblog.rust-lang.org
yakubin2 years ago

I'm eagerly awaiting the Cargo feature "named profiles"[1]. It's already merged, but not yet announced to be planned for any specific Cargo version. It will allow users to create custom profiles with different build parameters from the standard ones, so you can e.g. create a "profiling" profile which is based on the "release" profile, so that it has all the nice optimizations, but with debug information included, so that it works well with cargo-flamegraph:

  [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.

[2]: <https://github.com/rust-lang/rust/pull/89086>

steveklabnik2 years ago

Unless something is special about this feature, new features generally aren't "planned" for specific versions in general. Since this landed two weeks ago, that means that it should land in the beta version of Cargo once that branches for this release (I didn't check if it has yet or not), which would place it to be stable in the next release of Rust, 1.57.0.

lfairy2 years ago

BTW, this article explains how Rust releases work:

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.

yakubin2 years ago

Thanks for the clarification. I just went by what's specified in the sidebar on GitHub in the "Milestone" section.

steveklabnik2 years ago

Any time. Cargo doesn't use the Milestone feature of GitHub at all, so that's why that's empty :)

stusmall2 years ago

I love the concept of editions. I haven't anything like it in other languages. Having the ability to have one project made up of crates of many different editions is brilliant. It makes breaking language changes that would otherwise be a nonstarter become minor and easy to manage. Love it

steveklabnik2 years ago

While there's nothing exactly like it yet, we did consider the ways that many similar systems work. There's a lot of similarities, even though I do think Editions end up being meaningfully distinct.

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")

avgcorrection2 years ago

> I think the main problem here is semver.

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”).

steveklabnik2 years ago

While semver was intended as machine-readable versioning, and still mostly is, given that sevmer does not say what an "api" is in any way means that it's still defined by humans at the end of the day.

I think I'm in agreement :)

+1
stouset2 years ago
avgcorrection2 years ago

And I agree with you on the solution: version the language (or whatever other more conceptual thing) by year and the program by semver. It’s the only thing that makes sense given the constraints.

xamolxix2 years ago

> I haven't anything like it in other languages

Is that different from compiling one lib with --std=c++11 and another with --std=c++17?

masklinn2 years ago

Aside from the header issue, it doesn't allow for or support backwards-incompatible changes.

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.

neandrake2 years ago

It’s probably worth noting that the backwards compatibility breaking changes I think only applies to the language and not the standard library. I’m unable to look up the details right now but I believe there’s a trait function defined in stdlib which is deprecated, supersede, but can’t be removed even as part of an Edition, or it would break older code.

+1
masklinn2 years ago
EdSchouten2 years ago

Yes, in that in C++ it would break if one library used a modern language feature as part of its public header files.

chc2 years ago

Surely the difference there is that Rust doesn't have header files.

+2
masklinn2 years ago
hiccuphippo2 years ago

Can't you compile libraries to object files independently with whatever version of c++ they require and then link them together?

tialaramex2 years ago

Yes. If the only value the library brings to you is that it generates a particular object file, you can divorce that object file from your chosen language version entirely.

If 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.

MaulingMonkey2 years ago

The problem is a C++ library's headers must be compiled with the settings, context, and flags of every downstream thing that depends on them, rather than separately.

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.

int_19h2 years ago

You'd still need the header that describes the public interface of such a separately compiled object file.

Also, this doesn't work with templates, and modern idiomatic C++ tends to be template-heavy.

pornel2 years ago

Yes, because C versions are frozen in time, and editions aren't. Today Rust added brand new features to Rust 2015 and Rust 2018, and will continue to expand them forever (every new feature lands in all editions whenever possible).

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).

alerighi2 years ago

C compilers usually backport features in older standards as well (I know, they are breaking the standard). One example are C++ style comments, that are in the standard only from C99, but basically every compiler supports them even in ANSI/C90 mode.

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.

nicoburns2 years ago

> 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.

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.

lfairy2 years ago

> By the way the difference is that Rust is not a standard, thus is easier to evolve (the process is much shorter).

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++-...

heftig2 years ago

If the application is using C++17, wouldn't the headers of the C++11 lib then be compiled as C++17, potentially breaking things?

PS: The other way around is more obviously broken, with the C++17 lib headers getting compiled using C++11.

pjmlp2 years ago

Or you make use of the preprocessor or if constexpr, and then have the specific code for each language version.

tialaramex2 years ago

It's certainly true that if C++ library maintainers are up for the ever-growing maintenance burden, they can all individually deliver the same promise Rust gets out of the box.

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
int_19h2 years ago
orra2 years ago

I don't know what guarantees your typical C++ compiler gives you that those can link together?

Regardless, at the very least, you would need to write the headers to be interoperable.

cygx2 years ago

> I haven't anything like it in other languages

Perl can do it not only at module level, but at block level within a single source file.

remram2 years ago

Lots of languages have opt-in features/extensions/pragmas, which are more granular but somewhat similar. Like Python's __future__, Haskell's #LANGUAGE, and Rust's #![feature].

Adding 20 lines of #[!feature(...)] to every project would get old quick.

bee_rider2 years ago

Although if they hit Rust version 2.XX.Y this might add some (probably brief) confusion.

RussianCow2 years ago

I remember reading somewhere that Rust will never have a v2, but now I can't find a source.

nicoburns2 years ago

There are currently no plans, but I don’t think it’s a hard never. I’d probably be willing to bet it’ll be at least 10 years though.

emteycz2 years ago

ES(year) for JavaScript

nicoburns2 years ago

A better JavaScript example would be strict mode, which is opt-in with "use strict" and changes quite a bit of the semantics of the code while being completely interoperable with non-strict JS code.

Macha2 years ago

Yeah, and the "use" statements for this came from Perl which is probably the originator of this pattern here.

RussianCow2 years ago

Newer versions of JavaScript do not break compatibility with older versions, whereas different editions of Rust break compatibility in various ways while still allowing interoperability between libraries written in different editions. That makes the two approaches very different.

nivertech2 years ago

Solidity (a PL in which Ethereum's so called "smart contracts"[1] are developed) has version pragmas [2] at the top of each file.

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...

tialaramex2 years ago

From one your links:

> 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.

woodruffw2 years ago

Version pragmas themselves aren't new -- even Perl has them[1]. What makes Rust's implementation nice (and somewhat unique) is its commitment to backwards compatibility while allowing codebases to incrementally move towards a new edition.

[1]: https://perldoc.perl.org/functions/use

mbStavola2 years ago

I'm so excited to finally have disjoint captures in closures; many papercuts on that one when I worked heavily with Amethyst a while back as there were a few closure-based APIs.

Thanks to all the contributors for getting us to the 2021 edition!

k__2 years ago

That's the stuff Rust needs more of!

mjw10072 years ago

In 2017 or so when Rust editions were invented, the notion was that editions were primarily a "rallying point" (a way to make Rust's continuous release process feel more like Java's or C++'s), and only secondarily an (opt-in) change to the language itself.

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".)

pornel2 years ago

It created confusion, because:

• 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.

nicoburns2 years ago

The 2018 was widely considered to be overly pressured (contrary to the usual Rust approach of shipping it when it’s ready), which led to burnt out compiler devs, and a bunch of people with unmet expectations. I think the approach to decouple Big Bang features from editions has been made in response to that.

mjw10072 years ago

It's hard to tell how much of the trouble with 2018 was due to the concept of having a major release with several things being updated together, and how much was due to the mistake of setting too early a deadline (by pre-announcing that it would be "Rust 2018" rather than "Rust 2019").

kzrdude2 years ago

2021 was explicitly a "nothing new" edition, at least.

tialaramex2 years ago

Interesting. It mentions stuff like the library and documentation.

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.

steveklabnik2 years ago

It was discussed as part of the 2021 Edition RFC.

jjice2 years ago

Really excited for the reserved prefixes. Lots of possibilities there. I'm really rooting for f-strings to stream line format! calls.

eska2 years ago

Finally a nice way to init hash maps and all other kinds of collections! The former was really unattractive to Rust newbies!

dthul2 years ago

Love the 2021 edition changes and also the fact that they are so minor!

nynx2 years ago

Woot woot, transmute is const now

orra2 years ago

Curious what the applications are. Collections? Would you write a lot of "unsafe" const code, given the chance?

steveklabnik2 years ago

One thing that's come up a couple times are... well, what transmute is, that is, two types that have the same representation, and you want to cast between them. For example, the "try Rust out in the Linux kernel" code has a function that turns a byte slice into a &Cstr via a transmute. Those kinds of conversions can be useful inside of const fns.

ishanjain282 years ago

Hi Steve,

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

MaulingMonkey2 years ago

Per transmute's docs @ https://doc.rust-lang.org/std/mem/fn.transmute.html:

> 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...

steveklabnik2 years ago

I am unsure about the specific details about IR that's emitted (though at first glance it seems we do emit the alignment attribute now, I think?), but transmute is, as the documentation says, "incredibly" unsafe:

> 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."

jarpadat2 years ago

That discussion is a bit strange. In that example Foo is implicitly #[repr(Rust)] meaning it has an undefined layout. In particular, a,b is unordered, and even if you don't care which order you get, there is the question of padding (a reasonable compiler will tightly pack a,b but that's not required).

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.

luizfelberti2 years ago

I will shamefully admit to using transmute in very sinful ways, such as having enums that are repr(u16) or something like that at multiple numeric ranges such as:

    #[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 wizard

I'm not proud of this...

lilyball2 years ago

This sounds like a great example of where you might want a union (https://doc.rust-lang.org/1.56.0/reference/items/unions.html)

+1
luizfelberti2 years ago
kzrdude2 years ago

Happy that the const subset of the language grows

brink2 years ago

It's useful for handling data that came from outside Rust.

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.

nynx2 years ago

I can't think of an example now, but it has come up before when working on collections and other things.

nayuki2 years ago

I'm confused about the bullet point "IntoIterator for arrays". I thought this was added already in Rust 1.53.0. Did something else happen?

See https://blog.rust-lang.org/2021/06/17/Rust-1.53.0.html#whats...

steveklabnik2 years ago

There's an explanation at the bottom of the section; this landed as a hack in previous editions, but works via the normal mechanisms in Rust 2021.

"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.

davisoneee2 years ago

To follow Steve's comment:

> 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...

steveklabnik2 years ago

Right, but the point is that you can do that as of 1.53.0, you didn't have to wait until this release and the edition to do it.

tialaramex2 years ago

You know all this, but while some obvious things work in 1.53.0 one important thing causes scary warnings because it is shadowed by the back compat hack.

"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.

pjmlp2 years ago

Finally here! Congratulations to everyone.

Fiahil2 years ago

It's not written in the announcement, but I am under the impression that Rust 1.56 compiles a bit faster. Is that right ?

Macha2 years ago

Here's some compile benchmarks from the LLVM update which is the main reason for compile time changes:

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.

dralley2 years ago

Does anyone know what change was responsible for the big improvement from a few days ago?

https://perf.rust-lang.org

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.

kzrdude2 years ago

From the graph, it points out this commit. And from the description, I think it's exactly that commit and not just one close to it. I heard it was a big improvement.

https://github.com/rust-lang/rust/commit/63cc2bb

> Enable new pass manager with LLVM 13

> https://github.com/rust-lang/rust/pull/88243

dralley2 years ago

I'm talking about the change on the 17th of October, only a couple of days ago, not the one from a month ago which I know (and mentioned) is the new pass manager. The change doesn't show up in all of the graphs, but for the cases where it does show, it's a similar size improvement to the new pass manager.

Maybe it was enabling PGO rather than any code change, I've heard it mentioned that happened recently.

benschulz2 years ago

I believe this is mostly due to the switch to LLVM 13[1].

[1]: https://twitter.com/ryan_levick/status/1443202538099073027

nicoburns2 years ago

I believe the new pass manager isn’t due to be enabled by default until the next version (1.57)

runevault2 years ago

Yeah the significant improvements from 13 will require that last I heard.

eska2 years ago

That seems to have been a mixed bag. But they also enabled PGO (or was it LTO?) and that was mentioned to be a bigger improvement.

woodruffw2 years ago

PGO requires a runtime profile, so I doubt they've enabled that by default :-)

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.

+1
mastax2 years ago
Luker882 years ago

Wonderful, but still disappointed that easy strict-type-wrapping is not a thing yet.

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 :(