Author | Mara BosTranslator | SambodhiEditor | Cai FangfangRust 2021, the third edition of the Rust language, is scheduled for release in October. Rust 2021 includes several minor changes, yet they are expected to significantly improve the experience of using Rust in practical applications.What is a version?
As part of Rust’s update principles, the release of Rust 1.0 established a “stability without stagnation” approach. Since version 1.0, the rule for Rust has been that once a feature is released in a stable version, we will guarantee its support in all future versions.
However, sometimes it is useful to make small changes to the language that are not backward compatible. An obvious example is the introduction of a new keyword that invalidates variables with the same name. For instance, the first version of Rust did not have the keywords async and await. If these keywords suddenly became keywords in later versions, it would break code like let async = 1;.
Versions are our mechanism for addressing this issue. When we want to release a feature that is inherently backward incompatible, we include it as part of a new version of Rust. Versions are optional, so existing packages (crates) will not see these changes unless they explicitly migrate to the new version. This means that even the latest version of Rust will not treat async as a keyword unless the 2018 edition or a newer version is chosen. For each package, this choice is part of Cargo.toml. New packages created with cargo new are always configured to use the latest stable version.
Versions do not split the ecosystem
The most important rule for versions is that packages in one version can seamlessly interoperate with packages compiled in other versions. This ensures that the decision to migrate a package to a newer version is a “private decision” that can be made without affecting others.
The need for package interoperability imposes some limitations on the various changes we can make in a version. Generally, changes that occur in a version tend to be “surface-level”. However, regardless of the version, all Rust code will ultimately be compiled into the same internal representation in the compiler.
Migrating versions is easy and largely automated
Our goal is to make it easy for packages to upgrade to new versions. When a new version is released, we also provide tools for automatic migration. These tools make necessary minor changes to the code to ensure compatibility with the new version. For example, when migrating to Rust 2018, it converts everything called async to use the equivalent raw identifier syntax: r#async.
However, automatic migration is not perfect: there may still be cases that require manual changes. The tool strives to avoid changing semantics to prevent affecting the correctness or performance of the code.
In addition to the tools, we maintain a version migration guide that includes the changes included in the version. This guide will describe these changes and provide guidance for people to learn more. It will also include any edge cases or details that people need to be aware of. The guide can serve as an overview of the version or as a quick reference for troubleshooting when people encounter issues with the automation tools.
What changes are planned for Rust 2021?
The Rust 2021 working group has researched many suggestions for what the new version should include over the past few months. We are pleased to announce the final list of version changes. Each feature must meet two criteria to be included in this list. First, they must be approved by the relevant Rust team. Second, their implementation must be sufficiently advanced to ensure that they will be completed on time before the planned milestone.
Supplement to the prelude
The prelude module of the standard library contains everything that is automatically imported into every module. This includes commonly used items such as Option, Vec, drop, and Clone.
To ensure that additions to the prelude do not break any existing code, the Rust compiler will prioritize any manually imported items over those in the prelude. For example, if you have a package or module named example that contains a pub struct Option;, then use example::*; will explicitly refer to the one in example rather than the one in the standard library.
However, adding traits to the prelude will subtly break existing code. Calling x.try_into() with the MyTryInto trait may become ambiguous if std’s TryInto is also imported, as it provides a method with the same name. For this reason, we have not added TryInto to the prelude, as many codes would be broken in this way.
Rust 2021 will use a new prelude as a solution. Like now, it will only add three new items:
-
std::convert::TryInto
-
std::convert::TryFrom
-
std::iter::FromIterator
Default Cargo feature resolver
Starting from Rust 1.51.0, Cargo has provided support for a new feature resolver option, which can be activated in Cargo.toml with resolver = “2”.
From Rust 2021 onwards, this will be the default. This means that writing edition = “2021” in Cargo.toml will imply resolver = “2”.
The new feature resolver no longer merges all requested features of packages that depend on multiple ways; see the announcement for Rust 1.51 for details.
IntoIterator for arrays
Before Rust 1.53, IntoIterator was only implemented for references to arrays. This means you could iterate over &[1, 2, 3] and &mut [1, 2, 3], but you could not directly iterate over [1, 2, 3].
for &e in &[1, 2, 3] {} // Ok :)for e in [1, 2, 3] {} // Error :(
This issue has existed for a long time, but the solution is not as straightforward as it seems. Simply adding a trait implementation would break existing code. Now, array.into_iter() can be compiled because this function implicitly calls (&array).into_iter(), due to how method call syntax works. Adding a trait implementation would change this meaning.
In general, we categorize such breakage (adding trait implementations) as “minor”, which is acceptable. However, in this case, too much code would be broken.
Many people suggested “only implement IntoIterator for arrays in Rust 2021”. However, this is completely impossible. You cannot implement a feature in one version without implementing it in another, as versions may be mixed.
Instead, we decided to add the trait implementation (starting from Rust 1.53.0) to all versions, but with a small trick to avoid confusion before Rust 2021. From the perspective of Rust 2015 and 2018 code, the compiler will still resolve array.into_iter() as (&array).into_iter() as if the trait implementation does not exist. This only applies to the syntax of the .into_iter() method call. It will not affect any other syntax, such as for e in [1, 2, 3], iter.zip([1, 2, 3]), or IntoIterator::into_iter([1, 2, 3]). These features will be present in all versions.
Although this requires a small trick to avoid breakage, it is a shameful practice, but we are pleased that this solution minimizes the differences between versions. Since this small trick only exists in older versions, it will not add complexity in the new version.
Non-overlapping captures in closures
Closures automatically capture anything you reference in their body. For example, || a + 1 automatically captures a reference to a in the surrounding context.
Now, this applies to the entire structure, even if only one field is referenced. For example, || a.x + 1 captures a reference to a rather than just a.x. Sometimes, this can be problematic. If one field in a structure has already been borrowed (mutably) or moved out, then other fields cannot be used in the closure because doing so would capture the entire structure that is no longer available.
let a = SomeStruct::new();drop(a.x); // Move out of one field of the structprintln!("{}", a.y); // Ok: Still use another field of the structlet c = || println!("{}", a.y); // Error: Tries to capture all of a
Starting from Rust 2021, closures will only capture the fields they use. Therefore, the above example will compile correctly in Rust 2021.
This new behavior is only activated in the new version, as it can change the order of field removals. It is important to note that automatic migration can be used for all versions, and it will update your closures. You can insert let _ = &a; into the closure, which will require capturing the entire structure as before.
Panic macro consistency
The panic!() macro is one of the most well-known macros in Rust. However, it has some subtle quirks due to backward compatibility, which we cannot change arbitrarily.
panic!("{}", 1); // Ok, panics with the message "1"panic!("{}"); // Ok, panics with the message "{}"
The panic!() macro only uses string formatting when called with multiple parameters. If called with a single parameter, it does not even check that parameter.
let a = "{";println!(a); // Error: First argument must be a format string literalpanic!(a); // Ok: The panic macro doesn't care
(It even accepts non-strings, such as panic!(123), which is very rare and not very useful.)
When implicit format parameters stabilize, this will be a particular issue. This feature will make println!(“hello {name}”) a shorthand for println!(“hello {}”, name). However, panic!(“hello {name}”) will not work as expected because panic!() does not treat a single parameter as a format string.
Rust 2021 adopts a more consistent panic!() macro to avoid this confusion. The new panic!() macro will no longer accept arbitrary expressions as the sole parameter. It will always treat the first parameter as a formatted string, just like println!(). Since panic!() will no longer accept arbitrary payloads, panic_any() will be the only way to panic with a non-formatted string.
Additionally, core::panic!() and std::panic!() will be the same in Rust 2021. Currently, there are some historical differences between the two, which can be clearly seen when switching #![no_std].
Reserved syntax
To leave room for future new syntax, we have decided to reserve the syntax for prefix identifiers and literal symbols: prefix#identifier, prefix “string”, prefix’c’, and prefix#123, where the prefix can be any identifier. (Except for those that already have meaning, such as b’…’ and r”…”).
This is a significant change because macros can currently accept hello”world”, treating it as two separate tokens: hello and “world”. But (automatic) fixes are very simple. Just insert a space: hello “world”.
The RFC has not given any prefix any meaning other than turning these into tokenization errors. Assigning specific meanings to prefixes will be left to future proposals, and since these prefixes are now reserved, they will not undergo significant changes.
Here are some new prefixes you might see in the future:
-
f”” as a shorthand for format strings. For example, f”hello {name}” as a shorthand for a format_args!() call.
-
c”” or z”” representing null-terminated C language strings.
-
k#keyword allows writing keywords that do not yet exist in the current version. For example, while async is not a keyword in the 2015 edition, this prefix allows us to accept k#async as an alternative in the 2015 edition while waiting for the 2018 edition to make async a keyword.
Elevating two warnings to hard errors
Two existing lints are becoming hard errors in Rust 2021. In previous versions, these lints were still warnings.
-
bare-trait-objects: In Rust 2021, the dyn keyword must be used to identify trait objects.
-
ellipsis-inclusive-range-patterns: In Rust 2021, the deprecated … syntax will no longer be accepted for inclusive range patterns. Use ..= instead, consistent with expressions.
Or patterns in macro_rules
Starting from Rust 1.53.0, patterns have been expanded to support | nesting at any position in patterns. This allows you to write Some(1 | 2) without having to write Some(1) | Some(2). This is not a big change, as this was not allowed before.
However, this change will also affect macro_rules macros. Such macros can accept patterns specified with the :pat fragment specifier. Currently, :pat does not match |, as before Rust 1.53, not all patterns (at all nesting levels) could include |. Macros like matches!() accept patterns like A | B, using something similar to $($_:pat)|+. Since we do not want to break any existing macros, the meaning of :pat was not changed in Rust 1.53.0 to include |.
Instead, as part of Rust 2021, we will make this change. For the new version, the :pat fragment specifier will match A | B.
Since at some point, people may still want to match a single pattern variant without using |, a specified :pat_param fragment has been added to retain the old behavior. This name refers to its primary use case: patterns in closure parameters.
What’s next?
Our plan is to merge these modifications and conduct comprehensive testing before September to ensure that Rust 2021 can be included in Rust 1.56.0. Rust 1.56.0 will then undergo six weeks of testing, after which it will be released as a stable version on October 21.
However, it is important to note that Rust is a volunteer-driven project. We prioritize the well-being of everyone working on Rust over any deadlines and expectations we may set. If necessary, this means delaying the release or dropping a feature that is difficult to implement or too tight to complete on time.
That said, we are on track, and many challenges have been resolved, thanks to everyone who has contributed to Rust 2021!
Author Bio:Mara Bos, representative of the Rust 2021 working group.Original link:https://blog.rust-lang.org/2021/05/11/edition-2021.htmlThis week’s recommended articles Left hand VM, right hand Container Serverless, a comprehensive technical reveal behind the 2.4 billion user super APP Tencent announces open-source RoP: Apache Pulsar supports native RocketMQ protocol A high-priced stock caused the Nasdaq system to “collapse”!
InfoQ reader group is now online! Friends can scan the QR code below, add the InfoQ assistant, and reply with the keyword “Join group” to apply for group membership. Reply “Materials” to get the materials package portal. After registering on the InfoQ website, you can receive a free Geek Time course! You can chat freely with InfoQ readers, have close contact with editors, and receive valuable technical gifts, as well as participate in exciting activities. Come join us!
Click to see less bugs 👇