Introduction
Welcome to the Rust Edition Guide! "Editions" are Rust's way of introducing changes into the language that would not otherwise be backwards compatible.
In this guide, we'll discuss:
- What editions are
- Which changes are contained in each edition
- How to migrate your code from one edition to another
What are Editions?
The release of Rust 1.0 established "stability without stagnation" as a core Rust deliverable. Ever since the 1.0 release, the rule for Rust has been that once a feature has been released on stable, we are committed to supporting that feature for all future releases.
There are times, however, when it is useful to be able to make small changes
to the language that are not backwards compatible.
The most obvious example is introducing a new keyword,
which would invalidate variables with the same name.
For example, the first version of Rust did not have the async
and await
keywords.
Suddenly changing those words to keywords in a later version would've broken code like let async = 1;
.
Editions are the mechanism we use to solve this problem.
When we want to release a feature that would otherwise be backwards incompatible,
we do so as part of a new Rust edition.
Editions are opt-in, and so existing crates do
not see these changes until they explicitly migrate over to the new edition.
This means that even the latest version of Rust will still not treat async
as a keyword,
unless edition 2018 or later is chosen.
This choice is made per crate as part of its Cargo.toml
.
New crates created by cargo new
are always configured to use the latest stable edition.
Editions do not split the ecosystem
The most important rule for editions is that crates in one edition can interoperate seamlessly with crates compiled in other editions. This ensures that the decision to migrate to a newer edition is a "private one" that the crate can make without affecting others.
The requirement for crate interoperability implies some limits on the kinds of changes that we can make in an edition. In general, changes that occur in an edition tend to be "skin deep". All Rust code, regardless of edition, is ultimately compiled to the same internal representation within the compiler.
Edition migration is easy and largely automated
Our goal is to make it easy for crates to upgrade to a new edition.
When we release a new edition,
we also provide tooling to automate the migration.
It makes minor changes to your code necessary to make it compatible with the new edition.
For example, when migrating to Rust 2018, it changes anything named async
to use the equivalent
raw identifier syntax: r#async
.
The automated migrations are not necessarily perfect: there might be some corner cases where manual changes are still required. The tooling tries hard to avoid changes to semantics that could affect the correctness or performance of the code.
In addition to tooling, we also maintain this Edition Migration Guide that covers the changes that are part of an edition. This guide describes each change and gives pointers to where you can learn more about it. It also covers any corner cases or details you should be aware of. This guide serves both as an overview of the edition and as a quick troubleshooting reference if you encounter problems with the automated tooling.
Creating a new project
When you create a new project with Cargo, it will automatically add configuration for the latest edition:
> cargo +nightly new foo
Created binary (application) `foo` project
> cat .\foo\Cargo.toml
[package]
name = "foo"
version = "0.1.0"
authors = ["your name <you@example.com>"]
edition = "2018"
[dependencies]
That edition = "2018"
setting will configure your package to use Rust 2018.
No more configuration needed!
If you'd prefer to use an older edition, you can change the value in that key, for example:
[package]
name = "foo"
version = "0.1.0"
authors = ["your name <you@example.com>"]
edition = "2015"
[dependencies]
This will build your package in Rust 2015.
Transitioning an existing project to a new edition
New editions might change the way you write Rust β they add new syntax,
language, and library features, and also remove features. For example, try
,
async
, and await
are keywords in Rust 2018, but not Rust 2015. If you
have a project that's using Rust 2015, and you'd like to use Rust 2018 for it
instead, there's a few steps that you need to take.
It's our intention that the migration to new editions is as smooth an experience as possible. If it's difficult for you to upgrade to the latest edition, we consider that a bug. If you run into problems with this process, please file a bug. Thank you!
Here's an example. Imagine we have a crate that has this code in
src/lib.rs
:
#![allow(unused)] fn main() { trait Foo { fn foo(&self, Box<Foo>); } }
This code uses an anonymous parameter, that Box<Foo>
. This is not
supported in Rust 2018, and
so this would fail to compile. Let's get this code up to date!
Updating your code to be compatible with the new edition
Your code may or may not use features that are incompatible with the new edition. In order to help transition to Rust 2018, we've included a new subcommand with Cargo. To start, let's run it:
> cargo fix --edition
This will check your code, and automatically fix any issues that it can.
Let's look at src/lib.rs
again:
#![allow(unused)] fn main() { trait Foo { fn foo(&self, _: Box<Foo>); } }
It's re-written our code to introduce a parameter name for that trait object.
In this case, since it had no name, cargo fix
will replace it with _
,
which is conventional for unused variables.
cargo fix
can't always fix your code automatically.
If cargo fix
can't fix something, it will print the warning that it cannot fix
to the console. If you see one of these warnings, you'll have to update your code
manually. See the corresponding section of this guide for help, and if you have
problems, please seek help at the user's forums.
Keep running cargo fix --edition
until you have no more warnings.
Congrats! Your code is now valid in both Rust 2015 and Rust 2018!
Enabling the new edition to use new features
In order to use some new features, you must explicitly opt in to the new
edition. Once you're ready to commit, change your Cargo.toml
to add the new
edition
key/value pair. For example:
[package]
name = "foo"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]
edition = "2018"
If there's no edition
key, Cargo will default to Rust 2015. But in this case,
we've chosen 2018
, and so our code is compiling with Rust 2018!
Writing idiomatic code in a new edition
Editions are not only about new features and removing old ones. In any programming language, idioms change over time, and Rust is no exception. While old code will continue to compile, it might be written with different idioms today.
Our sample code contains an outdated idiom. Here it is again:
#![allow(unused)] fn main() { trait Foo { fn foo(&self, _: Box<Foo>); } }
In Rust 2018, it's considered idiomatic to use the dyn
keyword for
trait objects.
Eventually, we want cargo fix
to fix all these idioms automatically in the same
manner we did for upgrading to the 2018 edition. Currently,
though, the "idiom lints" are not ready for widespread automatic fixing. The
compiler isn't making cargo fix
-compatible suggestions in many cases right
now, and it is making incorrect suggestions in others. Enabling the idiom lints,
even with cargo fix
, is likely to leave your crate either broken or with many
warnings still remaining.
We have plans to make these idiom migrations a seamless part of the Rust 2018 experience, but we're not there yet. As a result the following instructions are recommended only for the intrepid who are willing to work through a few compiler/Cargo bugs!
With that out of the way, we can instruct Cargo to fix our code snippet with:
$ cargo fix --edition-idioms
Afterwards, src/lib.rs
looks like this:
#![allow(unused)] fn main() { trait Foo { fn foo(&self, _: Box<dyn Foo>); } }
We're now more idiomatic, and we didn't have to fix our code manually!
Note that cargo fix
may still not be able to automatically update our code.
If cargo fix
can't fix something, it will print a warning to the console, and
you'll have to fix it manually.
As mentioned before, there are known bugs around the idiom lints which
means they're not all ready for prime time yet. You may get a scary-looking
warning to report a bug to Cargo, which happens whenever a fix proposed by
rustc
actually caused code to stop compiling by accident. If you'd like cargo fix
to make as much progress as possible, even if it causes code to stop
compiling, you can execute:
$ cargo fix --edition-idioms --broken-code
This will instruct cargo fix
to apply automatic suggestions regardless of
whether they work or not. Like usual, you'll see the compilation result after
all fixes are applied. If you notice anything wrong or unusual, please feel free
to report an issue to Cargo and we'll help prioritize and fix it.
Enjoy the new edition!
Rust 2015
Rust 2015 has a theme of "stability". It commenced with the release of 1.0, and is the "default edition". The edition system was conceived in late 2017, but Rust 1.0 was released in May of 2015. As such, 2015 is the edition that you get when you don't specify any particular edition, for backwards compatibility reasons.
"Stability" is the theme of Rust 2015 because 1.0 marked a huge change in Rust development. Previous to Rust 1.0, Rust was changing on a daily basis. This made it very difficult to write large software in Rust, and made it difficult to learn. With the release of Rust 1.0 and Rust 2015, we committed to backwards compatibility, ensuring a solid foundation for people to build projects on top of.
Since it's the default edition, there's no way to port your code to Rust 2015; it just is. You'll be transitioning away from 2015, but never really to 2015. As such, there's not much else to say about it!
Rust 2018
Info | |
---|---|
RFC | #2052, which also proposed the Edition system |
Release version | 1.31.0 |
The edition system was created for the release of Rust 2018. The release of the Rust 2018 edition coincided with a number of other features all coordinated around the theme of productivity. The majority of those features were backwards compatible and are now available on all editions; however, some of those changes required the edition mechanism (most notably the module system changes).
Path and module system changes
Minimum Rust version: 1.31
Summary
- Paths in
use
declarations now work the same as other paths. - Paths starting with
::
must now be followed with an external crate. - Paths in
pub(in path)
visibility modifiers must now start withcrate
,self
, orsuper
.
Motivation
The module system is often one of the hardest things for people new to Rust. Everyone has their own things that take time to master, of course, but there's a root cause for why it's so confusing to many: while there are simple and consistent rules defining the module system, their consequences can feel inconsistent, counterintuitive and mysterious.
As such, the 2018 edition of Rust introduces a few new module system features, but they end up simplifying the module system, to make it more clear as to what is going on.
Here's a brief summary:
extern crate
is no longer needed in 99% of circumstances.- The
crate
keyword refers to the current crate. - Paths may start with a crate name, even within submodules.
- Paths starting with
::
must reference an external crate. - A
foo.rs
andfoo/
subdirectory may coexist;mod.rs
is no longer needed when placing submodules in a subdirectory. - Paths in
use
declarations work the same as other paths.
These may seem like arbitrary new rules when put this way, but the mental model is now significantly simplified overall. Read on for more details!
More details
Let's talk about each new feature in turn.
No more extern crate
This one is quite straightforward: you no longer need to write extern crate
to
import a crate into your project. Before:
// Rust 2015
extern crate futures;
mod submodule {
use futures::Future;
}
After:
// Rust 2018
mod submodule {
use futures::Future;
}
Now, to add a new crate to your project, you can add it to your Cargo.toml
,
and then there is no step two. If you're not using Cargo, you already had to pass
--extern
flags to give rustc
the location of external crates, so you'd just
keep doing what you were doing there as well.
One small note here:
cargo fix
will not currently automate this change. We may have it do this for you in the future.
An exception
There's one exception to this rule, and that's the "sysroot" crates. These are the crates distributed with Rust itself.
Usually these are only needed in very specialized situations. Starting in
1.41, rustc
accepts the --extern=CRATE_NAME
flag which automatically adds
the given crate name in a way similar to extern crate
. Build tools may use
this to inject sysroot crates into the crate's prelude. Cargo does not have a
general way to express this, though it uses it for proc_macro
crates.
Some examples of needing to explicitly import sysroot crates are:
std
: Usually this is not neccesary, becausestd
is automatically imported unless the crate is marked with#![no_std]
.core
: Usually this is not necessary, becausecore
is automatically imported, unless the crate is marked with#![no_core]
. For example, some of the internal crates used by the standard library itself need this.proc_macro
: This is automatically imported by Cargo if it is a proc-macro crate starting in 1.42.extern crate proc_macro;
would be needed if you want to support older releases, or if using another build tool that does not pass the appropriate--extern
flags torustc
.alloc
: Items in thealloc
crate are usually accessed via re-exports in thestd
crate. If you are working with ano_std
crate that supports allocation, then you may need to explicitly importalloc
.test
: This is only available on the nightly channel, and is usually only used for the unstable benchmark support.
Macros
One other use for extern crate
was to import macros; that's no longer needed.
Macros may be imported with use
like any other item. For example, the
following use of extern crate
:
#[macro_use]
extern crate bar;
fn main() {
baz!();
}
Can be changed to something like the following:
use bar::baz;
fn main() {
baz!();
}
Renaming crates
If you've been using as
to rename your crate like this:
extern crate futures as f;
use f::Future;
then removing the extern crate
line on its own won't work. You'll need to do this:
use futures as f;
use self::f::Future;
This change will need to happen in any module that uses f
.
The crate
keyword refers to the current crate
In use
declarations and in other code, you can refer to the root of the
current crate with the crate::
prefix. For instance, crate::foo::bar
will
always refer to the name bar
inside the module foo
, from anywhere else in
the same crate.
The prefix ::
previously referred to either the crate root or an external
crate; it now unambiguously refers to an external crate. For instance,
::foo::bar
always refers to the name bar
inside the external crate foo
.
Extern crate paths
Previously, using an external crate in a module without a use
import
required a leading ::
on the path.
// Rust 2015
extern crate chrono;
fn foo() {
// this works in the crate root
let x = chrono::Utc::now();
}
mod submodule {
fn function() {
// but in a submodule it requires a leading :: if not imported with `use`
let x = ::chrono::Utc::now();
}
}
Now, extern crate names are in scope in the entire crate, including submodules.
// Rust 2018
fn foo() {
// this works in the crate root
let x = chrono::Utc::now();
}
mod submodule {
fn function() {
// crates may be referenced directly, even in submodules
let x = chrono::Utc::now();
}
}
No more mod.rs
In Rust 2015, if you have a submodule:
// This `mod` declaration looks for the `foo` module in
// `foo.rs` or `foo/mod.rs`.
mod foo;
It can live in foo.rs
or foo/mod.rs
. If it has submodules of its own, it
must be foo/mod.rs
. So a bar
submodule of foo
would live at
foo/bar.rs
.
In Rust 2018 the restriction that a module with submodules must be named
mod.rs
is lifted. foo.rs
can just be foo.rs
,
and the submodule is still foo/bar.rs
. This eliminates the special
name, and if you have a bunch of files open in your editor, you can clearly
see their names, instead of having a bunch of tabs named mod.rs
.
Rust 2015 | Rust 2018 |
---|---|
. βββ lib.rs βββ foo/ Β Β βββ mod.rs Β Β βββ bar.rs |
. βββ lib.rs βββ foo.rs βββ foo/ Β Β βββ bar.rs |
use
paths
Minimum Rust version: 1.32
Rust 2018 simplifies and unifies path handling compared to Rust 2015. In Rust
2015, paths work differently in use
declarations than they do elsewhere. In
particular, paths in use
declarations would always start from the crate
root, while paths in other code implicitly started from the current scope.
Those differences didn't have any effect in the top-level module, which meant
that everything would seem straightforward until working on a project large
enough to have submodules.
In Rust 2018, paths in use
declarations and in other code work the same way,
both in the top-level module and in any submodule. You can use a relative path
from the current scope, a path starting from an external crate name, or a path
starting with crate
, super
, or self
.
Code that looked like this:
// Rust 2015
extern crate futures;
use futures::Future;
mod foo {
pub struct Bar;
}
use foo::Bar;
fn my_poll() -> futures::Poll { ... }
enum SomeEnum {
V1(usize),
V2(String),
}
fn func() {
let five = std::sync::Arc::new(5);
use SomeEnum::*;
match ... {
V1(i) => { ... }
V2(s) => { ... }
}
}
will look exactly the same in Rust 2018, except that you can delete the extern crate
line:
// Rust 2018
use futures::Future;
mod foo {
pub struct Bar;
}
use foo::Bar;
fn my_poll() -> futures::Poll { ... }
enum SomeEnum {
V1(usize),
V2(String),
}
fn func() {
let five = std::sync::Arc::new(5);
use SomeEnum::*;
match ... {
V1(i) => { ... }
V2(s) => { ... }
}
}
The same code will also work completely unmodified in a submodule:
// Rust 2018
mod submodule {
use futures::Future;
mod foo {
pub struct Bar;
}
use foo::Bar;
fn my_poll() -> futures::Poll { ... }
enum SomeEnum {
V1(usize),
V2(String),
}
fn func() {
let five = std::sync::Arc::new(5);
use SomeEnum::*;
match ... {
V1(i) => { ... }
V2(s) => { ... }
}
}
}
This makes it easy to move code around in a project, and avoids introducing additional complexity to multi-module projects.
If a path is ambiguous, such as if you have an external crate and a local
module or item with the same name, you'll get an error, and you'll need to
either rename one of the conflicting names or explicitly disambiguate the path.
To explicitly disambiguate a path, use ::name
for an external crate name, or
self::name
for a local module or item.
Anonymous trait function parameters deprecated
Minimum Rust version: 1.31
Summary
- Trait function parameters may use any irrefutable pattern when the function has a body.
Details
In accordance with RFC #1685, parameters in trait method declarations are no longer allowed to be anonymous.
For example, in the 2015 edition, this was allowed:
#![allow(unused)] fn main() { trait Foo { fn foo(&self, u8); } }
In the 2018 edition, all parameters must be given an argument name (even if it's just
_
):
#![allow(unused)] fn main() { trait Foo { fn foo(&self, baz: u8); } }
New keywords
Minimum Rust version: 1.27
Summary
dyn
is a strict keyword, in 2015 it is a weak keyword.async
andawait
are strict keywords.try
is a reserved keyword.
Motivation
dyn Trait
for trait objects
The dyn Trait
feature is the new syntax for using trait objects. In short:
Box<Trait>
becomesBox<dyn Trait>
&Trait
and&mut Trait
become&dyn Trait
and&mut dyn Trait
And so on. In code:
#![allow(unused)] fn main() { trait Trait {} impl Trait for i32 {} // old fn function1() -> Box<Trait> { unimplemented!() } // new fn function2() -> Box<dyn Trait> { unimplemented!() } }
That's it!
Why?
Using just the trait name for trait objects turned out to be a bad decision. The current syntax is often ambiguous and confusing, even to veterans, and favors a feature that is not more frequently used than its alternatives, is sometimes slower, and often cannot be used at all when its alternatives can.
Furthermore, with impl Trait
arriving, "impl Trait
vs dyn Trait
" is much
more symmetric, and therefore a bit nicer, than "impl Trait
vs Trait
".
impl Trait
is explained here.
In the new edition, you should therefore prefer dyn Trait
to just Trait
where you need a trait object.
async
and await
These keywords are reserved to implement the async-await feature of Rust, which was ultimately released to stable in 1.39.0.
try
keyword
The try
keyword is reserved for use in try
blocks, which have not (as of this writing) been stabilized (tracking issue)
Method dispatch for raw pointers to inference variables
Summary
- The
tyvar_behind_raw_pointer
lint is now a hard error.
Details
See Rust issue #46906 for details.
Cargo changes
Summary
- If there is a target definition in a
Cargo.toml
manifest, it no longer automatically disables automatic discovery of other targets. - Target paths of the form
src/{target_name}.rs
are no longer inferred for targets where thepath
field is not set. cargo install
for the current directory is no longer allowed, you must specifycargo install --path .
to install the current package.
Rust 2021
π§ The 2021 Edition has not yet been released and hence this section is still "under construction". You can read more about our plans in this blog post.
Info | |
---|---|
RFC | #3085 |
Release version | 1.56.0 (anticipated) |
The Rust 2021 Edition is currently slated for release in Rust 1.56.0. Rust 1.56.0 will then be in beta for six weeks, after which it is released as stable on October 21st.
However, note that Rust is a project run by volunteers. We prioritize the personal well-being of everyone working on Rust over any deadlines and expectations we might have set. This could mean delaying the edition a version if necessary, or dropping a feature that turns out to be too difficult or stressful to finish in time.
That said, we are on schedule and many of the difficult problems are already tackled, thanks to all the people contributing to Rust 2021! π
Additions to the prelude
Summary
Details
The prelude of the standard library
is the module containing everything that is automatically imported in every module.
It contains commonly used items such as Option
, Vec
, drop
, and Clone
.
The Rust compiler prioritizes any manually imported items over those
from the prelude, to make sure additions to the prelude will not break any existing code.
For example, if you have a crate or module called example
containing a pub struct Option;
,
then use example::*;
will make Option
unambiguously refer to the one from example
;
not the one from the standard library.
However, adding a trait to the prelude can break existing code in a subtle way.
A call to x.try_into()
using a MyTryInto
trait might become ambiguous and
fail to compile if std
's TryInto
is also imported,
since it provides a method with the same name.
This is the reason we haven't added TryInto
to the prelude yet,
since there is a lot of code that would break this way.
As a solution, Rust 2021 will use a new prelude. It's identical to the current one, except for three new additions:
The library team still needs to formally approve these, which will likely happen soon.
Default Cargo feature resolver
Summary
Details
Since Rust 1.51.0, Cargo has opt-in support for a new feature resolver
which can be activated with resolver = "2"
in Cargo.toml
.
Starting in Rust 2021, this will be the default.
That is, writing edition = "2021"
in Cargo.toml
will imply resolver = "2"
.
The new feature resolver no longer merges all requested features for crates that are depended on in multiple ways. See the announcement of Rust 1.51 for details.
IntoIterator for arrays
Summary
Details
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.
for &e in &[1, 2, 3] {} // Ok :)
for e in [1, 2, 3] {} // Error :(
This has been a long-standing issue, but the solution is not as simple as it seems.
Just adding the trait implementation would break existing code.
array.into_iter()
already compiles today because that implicitly calls
(&array).into_iter()
due to how method call syntax works.
Adding the trait implementation would change the meaning.
Usually we categorize this type of breakage (adding a trait implementation) 'minor' and acceptable. But in this case there is too much code that would be broken by it.
It has been suggested many times to "only implement IntoIterator
for arrays in Rust 2021".
However, this is simply not possible.
You can't have a trait implementation exist in one edition and not in another,
since editions can be mixed.
Instead, we decided to add the trait implementation in all editions (starting in Rust 1.53.0),
but add a small hack to avoid breakage until Rust 2021.
In Rust 2015 and 2018 code, the compiler will still resolve array.into_iter()
to (&array).into_iter()
like before, as if the trait implementation does not exist.
This only applies to the .into_iter()
method call syntax.
It does 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])
.
Those will start to work in all editions.
While it's a shame that this required a small hack to avoid breakage, we're very happy with how this solution keeps the difference between the editions to an absolute minimum. Since the hack is only present in the older editions, there is no added complexity in the new edition.
Disjoint capture in closures
Summary
Details
Closures
automatically capture anything that you refer to from within their body.
For example, || a + 1
automatically captures a reference to a
from the surrounding context.
Currently, this applies to whole structs, even when only using one field.
For example, || a.x + 1
captures a reference to a
and not just a.x
.
In some situations, this is a problem.
When a field of the struct is already borrowed (mutably) or moved out of,
the other fields can no longer be used in a closure,
since that would capture the whole struct, which is no longer available.
let a = SomeStruct::new();
drop(a.x); // Move out of one field of the struct
println!("{}", a.y); // Ok: Still use another field of the struct
let c = || println!("{}", a.y); // Error: Tries to capture all of `a`
c();
Starting in Rust 2021, closures will only capture the fields that they use. So, the above example will compile fine in Rust 2021.
This new behavior is only activated in the new edition,
since it can change the order in which fields are dropped.
As for all edition changes, an automatic migration is available,
which will update your closures for which this matters.
It can insert let _ = &a;
inside the closure to force the entire
struct to be captured as before.
Panic macro consistency
Summary
Details
The panic!()
macro is one of Rust's most well known macros.
However, it has some subtle surprises
that we can't just change due to backwards compatibility.
panic!("{}", 1); // Ok, panics with the message "1"
panic!("{}"); // Ok, panics with the message "{}"
The panic!()
macro only uses string formatting when it's invoked with more than one argument.
When invoked with a single argument, it doesn't even look at that argument.
let a = "{";
println!(a); // Error: First argument must be a format string literal
panic!(a); // Ok: The panic macro doesn't care
(It even accepts non-strings such as panic!(123)
, which is uncommon and rarely useful.)
This will especially be a problem once
implicit format arguments
are stabilized.
That feature will make println!("hello {name}")
a short-hand for println!("hello {}", name)
.
However, panic!("hello {name}")
would not work as expected,
since panic!()
doesn't process a single argument as format string.
To avoid that confusing situation, Rust 2021 features a more consistent panic!()
macro.
The new panic!()
macro will no longer accept arbitrary expressions as the only argument.
It will, just like println!()
, always process the first argument as format string.
Since panic!()
will no longer accept arbitrary payloads,
panic_any()
will be the only way to panic with something other than a formatted string.
In addition, core::panic!()
and std::panic!()
will be identical in Rust 2021.
Currently, there are some historical differences between those two,
which can be noticable when switching #![no_std]
on or off.
Reserving syntax
Summary
Details
To make space for some new syntax in the future,
we've decided to reserve syntax for prefixed identifiers and literals:
prefix#identifier
, prefix"string"
, prefix'c'
, and prefix#123
,
where prefix
can be any identifier.
(Except those that already have a meaning, such as b'β¦'
and r"β¦"
.)
This is a breaking change, since macros can currently accept hello"world"
,
which they will see as two separate tokens: hello
and "world"
.
The (automatic) fix is simple though. Just insert a space: hello "world"
.
Other than turning these into a tokenization error, the RFC does not attach a meaning to any prefix yet. Assigning meaning to specific prefixes is left to future proposals, which willβthanks to reserving these prefixes nowβnot be breaking changes.
These are some new prefixes you might see in the future:
-
f""
as a short-hand for a format string. For example,f"hello {name}"
as a short-hand for the equivalentformat_args!()
invocation. -
c""
orz""
for null-terminated C strings. -
k#keyword
to allow writing keywords that don't exist yet in the current edition. For example, whileasync
is not a keyword in edition 2015, this prefix would've allowed us to acceptk#async
in edition 2015 without having to wait for edition 2018 to reserveasync
as a keyword.
Warnings promoted to errors
Summary
Details
Two existing lints are becoming hard errors in Rust 2021. These lints will remain warnings in older editions.
-
bare-trait-objects
: The use of thedyn
keyword to identify trait objects will be mandatory in Rust 2021. -
ellipsis-inclusive-range-patterns
: The deprecated...
syntax for inclusive range patterns is no longer accepted in Rust 2021. It has been superseded by..=
, which is consistent with expressions.
Or patterns in macro-rules
Summary
Details
Starting in Rust 1.53.0, patterns
are extended to support |
nested anywhere in the pattern.
This enables you to write Some(1 | 2)
instead of Some(1) | Some(2)
.
Since this was simply not allowed before, this is not a breaking change.
However, this change also affects macro_rules
macros.
Such macros can accept patterns using the :pat
fragment specifier.
Currently, :pat
does not match |
, since before Rust 1.53,
not all patterns (at all nested levels) could contain a |
.
Macros that accept patterns like A | B
,
such as matches!()
use something like $($_:pat)|+
.
Because we don't want to break any existing macros,
we did not change the meaning of :pat
in Rust 1.53.0 to include |
.
Instead, we will make that change as part of Rust 2021.
In the new edition, the :pat
fragment specifier will match A | B
.
Since there are times that one still wishes to match a single pattern
variant without |
, the fragment specified :pat_param
has been added
to retain the older behavior.
The name refers to its main use case: a pattern in a closure parameter.