[TOC]
The following lists contain keywords that are reserved for current or future use by the Rust language. As such, they cannot be used as identifiers (except as raw identifiers, as we’ll discuss in “Raw Identifiers” on page XX). Identifiers are names of functions, variables, parameters, struct fields, modules, crates, constants, macros, static values, attributes, types, traits, or lifetimes.
The following is a list of keywords currently in use, with their functionality described.
- **
as
**: perform primitive casting, disambiguate the specific trait containing an item, or rename items inuse
statements - **
async
**: return aFuture
instead of blocking the current thread - **
await
**: suspend execution until the result of aFuture
is ready - **
break
**: exit a loop immediately - **
const
**: define constant items or constant raw pointers - **
continue
**: continue to the next loop iteration - **
crate
**: in a module path, refers to the crate root - **
dyn
**: dynamic dispatch to a trait object - **
else
**: fallback forif
andif let
control flow constructs - **
enum
**: define an enumeration - **
extern
**: link an external function or variable - **
false
**: Boolean false literal - **
fn
**: define a function or the function pointer type - **
for
**: loop over items from an iterator, implement a trait, or specify a higher-ranked lifetime - **
if
**: branch based on the result of a conditional expression - **
impl
**: implement inherent or trait functionality - **
in
**: part offor
loop syntax - **
let
**: bind a variable - **
loop
**: loop unconditionally - **
match
**: match a value to patterns - **
mod
**: define a module - **
move
**: make a closure take ownership of all its captures - **
mut
**: denote mutability in references, raw pointers, or pattern bindings - **
pub
**: denote public visibility in struct fields,impl
blocks, or modules - **
ref
**: bind by reference - **
return
**: return from function - **
Self
**: a type alias for the type we are defining or implementing - **
self
**: method subject or current module - **
static
**: global variable or lifetime lasting the entire program execution - **
struct
**: define a structure - **
super
**: parent module of the current module - **
trait
**: define a trait - **
true
**: Boolean true literal - **
type
**: define a type alias or associated type - **
union
**: define a union; is a keyword only when used in a union declaration - **
unsafe
**: denote unsafe code, functions, traits, or implementations - **
use
**: bring symbols into scope - **
where
**: denote clauses that constrain a type - **
while
**: loop conditionally based on the result of an expression
The following keywords do not yet have any functionality but are reserved by Rust for potential future use:
abstract
become
box
do
final
macro
override
priv
try
typeof
unsized
virtual
yield
Raw identifiers are the syntax that lets you use keywords where they wouldn’t
normally be allowed. You use a raw identifier by prefixing a keyword with r#
.
For example, match
is a keyword. If you try to compile the following function
that uses match
as its name:
Filename: src/main.rs
fn match(needle: &str, haystack: &str) -> bool {
haystack.contains(needle)
}
you’ll get this error:
error: expected identifier, found keyword `match`
--> src/main.rs:4:4
|
4 | fn match(needle: &str, haystack: &str) -> bool {
| ^^^^^ expected identifier, found keyword
The error shows that you can’t use the keyword match
as the function
identifier. To use match
as a function name, you need to use the raw
identifier syntax, like this:
Filename: src/main.rs
fn r#match(needle: &str, haystack: &str) -> bool {
haystack.contains(needle)
}
fn main() {
assert!(r#match("foo", "foobar"));
}
This code will compile without any errors. Note the r#
prefix on the function
name in its definition as well as where the function is called in main
.
Raw identifiers allow you to use any word you choose as an identifier, even if
that word happens to be a reserved keyword. This gives us more freedom to
choose identifier names, as well as lets us integrate with programs written in
a language where these words aren’t keywords. In addition, raw identifiers
allow you to use libraries written in a different Rust edition than your crate
uses. For example, try
isn’t a keyword in the 2015 edition but is in the 2018
and 2021 editions. If you depend on a library that is written using the 2015
edition and has a try
function, you’ll need to use the raw identifier syntax,
r#try
in this case, to call that function from your 2021 edition code. See
Appendix E for more information on editions.
This appendix contains a glossary of Rust’s syntax, including operators and other symbols that appear by themselves or in the context of paths, generics, trait bounds, macros, attributes, comments, tuples, and brackets.
Table B-1 contains the operators in Rust, an example of how the operator would appear in context, a short explanation, and whether that operator is overloadable. If an operator is overloadable, the relevant trait to use to overload that operator is listed.
Table B-1: Operators
Operator | Example | Explanation | Overloadable? |
---|---|---|---|
! |
ident!(...) , ident!{...} , ident![...] |
Macro expansion | |
! |
!expr |
Bitwise or logical complement | Not |
!= |
expr != expr |
Nonequality comparison | PartialEq |
`% | expr % expr |
Arithmetic remainder | Rem |
%= |
var %= expr |
Arithmetic remainder and assignment | RemAssign |
`& | &expr , &mut expr |
Borrow | |
& |
&type , &mut type , &'a type , &'a mut type |
Borrowed pointer | |
type | |||
& |
expr & expr |
Bitwise AND | BitAnd |
&= |
var &= expr |
Bitwise AND and assignment | BitAndAssign |
&& |
expr && expr |
Short-circuiting logical AND | |
`* | expr * expr |
Arithmetic multiplication | Mul |
*= |
var *= expr |
Arithmetic multiplication and assignment | MulAssign |
|
| *
| *expr
| Dereference | Deref
|
| *
| *const type
, *mut type | Raw pointer | | |
+ | trait + trait
, 'a + trait
| Compound type constraint | |
| + |
expr + expr| Arithmetic addition |
Add| |
+=|
var += expr| Arithmetic addition and assignment |
AddAssign| |
,|
expr, expr| Argument and element separator | | |
- | - expr
| Arithmetic negation | Neg
|
| - |
expr - expr| Arithmetic subtraction |
Sub| |
-=|
var -= expr| Arithmetic subtraction and assignment |
SubAssign| |
-> | fn(...) -> type
, |…| -> type
| Function and closure return type | |
| . |
expr.ident| Member access | | |
..|
..,
expr..,
..expr,
expr..expr| Right-exclusive range literal |
PartialOrd| |
..=|
..=expr,
expr..=expr| Right-inclusive range literal |
PartialOrd| |
..|
..expr| Struct literal update syntax | | |
..|
variant(x, ..),
struct_type { x, .. }| “And the rest” pattern binding | | |
...|
expr...expr| (Deprecated, use
..=instead) In a pattern: inclusive range pattern | | |
/ | expr / expr
| Arithmetic division | Div
|
| /=
| var /= expr
| Arithmetic division and assignment | DivAssign
|
| : |
pat: type,
ident: type| Constraints | | |
:|
ident: expr| Struct field initializer | | |
:|
'a: loop {...}| Loop label | | |
; | expr;
| Statement and item terminator | |
| ;
| [...; len]
| Part of fixed-size array syntax | |
| <<
| expr << expr
| Left-shift | Shl
|
| <<=
| var <<= expr
| Left-shift and assignment | ShlAssign
|
| <
| expr < expr
| Less than comparison | PartialOrd
|
| <=
| expr <= expr
| Less than or equal to comparison | PartialOrd
|
| =
| var = expr
, ident = type
| Assignment/equivalence | |
| ==
| expr == expr
| Equality comparison | PartialEq
|
| =>
| pat => expr
| Part of match arm syntax | |
| >
| expr > expr
| Greater than comparison | PartialOrd
|
| >=
| expr >= expr
| Greater than or equal to comparison | PartialOrd
|
| >>
| expr >> expr
| Right-shift | Shr
|
| >>=
| var >>= expr
| Right-shift and assignment | ShrAssign
|
| @ |
ident @ pat| Pattern binding | | |
^|
expr ^ expr| Bitwise exclusive OR |
BitXor| |
^=|
var ^= expr| Bitwise exclusive OR and assignment |
BitXorAssign| |
| | pat | pat
| Pattern alternatives | |
| |
| expr | expr
| Bitwise OR | BitOr
|
| |=
| var |= expr
| Bitwise OR and assignment | BitOrAssign
|
| ||
| expr || expr
| Short-circuiting logical OR | |
| ? |
expr?` | Error propagation | |
The following tables contain all symbols that don’t function as operators; that is, they don’t behave like a function or method call.
Table B-2 shows symbols that appear on their own and are valid in a variety of locations.
Table B-2: Stand-Alone Syntax
Symbol | Explanation |
---|---|
`'ident | Named lifetime or loop label |
...u8 , ...i32 , ...f64 , ...usize , and so on |
Numeric literal of |
specific type | |
`"..." | String literal |
r"..." , r#"..."# , r##"..."## , and so on |
Raw string literal; escape |
characters not processed | |
b"..." |
Byte string literal; constructs an array of bytes instead of a |
string | |
br"..." , br#"..."# , br##"..."## , and so on |
Raw byte string literal; |
combination of raw and byte string literal | |
`'...' | Character literal |
`b'...' | ASCII byte literal |
` | … |
`! | Always-empty bottom type for diverging functions |
`_ | “Ignored” pattern binding; also used to make integer literals readable |
Table B-3 shows symbols that appear in the context of a path through the module hierarchy to an item.
Table B-3: Path-Related Syntax
Symbol | Explanation |
---|---|
`ident::ident | Namespace path |
::path |
Path relative to the crate root (that is, an explicitly absolute |
path) | |
self::path |
Path relative to the current module (that is, an explicitly |
relative path) | |
super::path |
Path relative to the parent of the current module |
type::ident , `::ident |
Associated constants, functions, and |
types | |
<type>::... |
Associated item for a type that cannot be directly named (for |
example, <&T>::... , <[T]>::... , and so on) |
|
trait::method(...) |
Disambiguating a method call by naming the trait that |
defines it | |
type::method(...) |
Disambiguating a method call by naming the type for |
which it’s defined | |
<type as trait>::method(...) |
Disambiguating a method call by naming the |
trait and type |
Table B-4 shows symbols that appear in the context of using generic type parameters.
Table B-4: Generics
Symbol | Explanation |
---|---|
path<...> |
Specifies parameters to a generic type in a type (for example, |
Vec<u8> ) |
|
path::<...>, method::<...> |
Specifies parameters to a generic type, |
function, or method in an expression; often referred to as turbofish (for | |
example, "42".parse::<i32>() ) |
|
fn ident<...> ... |
Define generic function |
struct ident<...> ... |
Define generic structure |
enum ident<...> ... |
Define generic enumeration |
impl<...> ... |
Define generic implementation |
for<...> type |
Higher-ranked lifetime bounds |
type<ident=type> |
A generic type where one or more associated types have |
specific assignments (for example, Iterator<Item=T> ) |
Table B-5 shows symbols that appear in the context of constraining generic type parameters with trait bounds.
Table B-5: Trait Bound Constraints
Symbol | Explanation |
---|---|
T: U` | Generic parameter T constrained to types that implement U |
T: 'a |
Generic type T must outlive lifetime 'a (meaning the type |
cannot transitively contain any references with lifetimes shorter than 'a ) |
|
T: 'static |
Generic type T contains no borrowed references other than |
'static ones |
|
'b: 'a |
Generic lifetime 'b must outlive lifetime 'a |
T: ?Sized |
Allow generic type parameter to be a dynamically sized type |
'a + trait , trait + trait |
Compound type constraint |
Table B-6 shows symbols that appear in the context of calling or defining macros and specifying attributes on an item.
Table B-6: Macros and Attributes
Symbol | Explanation |
---|---|
#[meta] |
Outer attribute |
#![meta] |
Inner attribute |
$ident |
Macro substitution |
$ident:kind |
Macro capture |
$(…)… |
Macro repetition |
ident!(...) , ident!{...} , ident![...] |
Macro invocation |
Table B-7 shows symbols that create comments.
Table B-7: Comments
Symbol | Explanation |
---|---|
// |
Line comment |
//! |
Inner line doc comment |
/// |
Outer line doc comment |
/*...*/ |
Block comment |
/*!...*/ |
Inner block doc comment |
/**...*/ |
Outer block doc comment |
Table B-8 shows symbols that appear in the context of using tuples.
Table B-8: Tuples
Symbol | Explanation |
---|---|
`() | Empty tuple (aka unit), both literal and type |
(expr) |
Parenthesized expression |
(expr,) |
Single-element tuple expression |
(type,) |
Single-element tuple type |
(expr, ...) |
Tuple expression |
(type, ...) |
Tuple type |
expr(expr, ...) |
Function call expression; also used to initialize tuple |
struct s and tuple enum variants |
|
expr.0 , expr.1 , and so on |
Tuple indexing |
Table B-9 shows the contexts in which curly brackets are used.
Table B-9: Curly Brackets
Context | Explanation |
---|---|
{...} |
Block expression |
Type {...} |
struct literal |
Table B-10 shows the contexts in which square brackets are used.
Table B-10: Square Brackets
Context | Explanation |
---|---|
[...] |
Array literal |
[expr; len] |
Array literal containing len copies of expr |
[type; len] |
Array type containing len instances of type |
expr[expr] |
Collection indexing; overloadable (Index , IndexMut ) |
expr[..] , expr[a..] , expr[..b] , expr[a..b] |
Collection indexing |
pretending to be collection slicing, using Range , RangeFrom , RangeTo , or |
|
RangeFull as the “index” |
In various places in the book, we’ve discussed the derive
attribute, which
you can apply to a struct or enum definition. The derive
attribute generates
code that will implement a trait with its own default implementation on the
type you’ve annotated with the derive
syntax.
In this appendix, we provide a reference of all the traits in the standard
library that you can use with derive
. Each section covers:
- What operators and methods deriving this trait will enable
- What the implementation of the trait provided by
derive
does - What implementing the trait signifies about the type
- The conditions in which you’re allowed or not allowed to implement the trait
- Examples of operations that require the trait
If you want different behavior from that provided by the derive
attribute,
consult the standard library documentation for each trait for details on how to
manually implement them.
The traits listed here are the only ones defined by the standard library that
can be implemented on your types using derive
. Other traits defined in the
standard library don’t have sensible default behavior, so it’s up to you to
implement them in the way that makes sense for what you’re trying to accomplish.
An example of a trait that can’t be derived is Display
, which handles
formatting for end users. You should always consider the appropriate way to
display a type to an end user. What parts of the type should an end user be
allowed to see? What parts would they find relevant? What format of the data
would be most relevant to them? The Rust compiler doesn’t have this insight, so
it can’t provide appropriate default behavior for you.
The list of derivable traits provided in this appendix is not comprehensive:
libraries can implement derive
for their own traits, making the list of
traits you can use derive
with truly open ended. Implementing derive
involves using a procedural macro, which is covered in “Macros” on page XX.
The Debug
trait enables debug formatting in format strings, which you
indicate by adding :?
within {}
placeholders.
The Debug
trait allows you to print instances of a type for debugging
purposes, so you and other programmers using your type can inspect an instance
at a particular point in a program’s execution.
The Debug
trait is required, for example, in the use of the assert_eq!
macro. This macro prints the values of instances given as arguments if the
equality assertion fails so programmers can see why the two instances weren’t
equal.
The PartialEq
trait allows you to compare instances of a type to check for
equality and enables use of the ==
and !=
operators.
Deriving PartialEq
implements the eq
method. When PartialEq
is derived on
structs, two instances are equal only if all fields are equal, and the
instances are not equal if any fields are not equal. When derived on enums,
each variant is equal to itself and not equal to the other variants.
The PartialEq
trait is required, for example, with the use of the
assert_eq!
macro, which needs to be able to compare two instances of a type
for equality.
The Eq
trait has no methods. Its purpose is to signal that for every value of
the annotated type, the value is equal to itself. The Eq
trait can only be
applied to types that also implement PartialEq
, although not all types that
implement PartialEq
can implement Eq
. One example of this is floating-point
number types: the implementation of floating-point numbers states that two
instances of the not-a-number (NaN
) value are not equal to each other.
An example of when Eq
is required is for keys in a HashMap<K, V>
so that
the HashMap<K, V>
can tell whether two keys are the same.
The PartialOrd
trait allows you to compare instances of a type for sorting
purposes. A type that implements PartialOrd
can be used with the <
, >
,
<=
, and >=
operators. You can only apply the PartialOrd
trait to types
that also implement PartialEq
.
Deriving PartialOrd
implements the partial_cmp
method, which returns an
Option<Ordering>
that will be None
when the values given don’t produce an
ordering. An example of a value that doesn’t produce an ordering, even though
most values of that type can be compared, is the not-a-number (NaN
) floating
point value. Calling partial_cmp
with any floating-point number and the NaN
floating-point value will return None
.
When derived on structs, PartialOrd
compares two instances by comparing the
value in each field in the order in which the fields appear in the struct
definition. When derived on enums, variants of the enum declared earlier in the
enum definition are considered less than the variants listed later.
The PartialOrd
trait is required, for example, for the gen_range
method
from the rand
crate that generates a random value in the range specified by a
range expression.
The Ord
trait allows you to know that for any two values of the annotated
type, a valid ordering will exist. The Ord
trait implements the cmp
method,
which returns an Ordering
rather than an Option<Ordering>
because a valid
ordering will always be possible. You can only apply the Ord
trait to types
that also implement PartialOrd
and Eq
(and Eq
requires PartialEq
). When
derived on structs and enums, cmp
behaves the same way as the derived
implementation for partial_cmp
does with PartialOrd
.
An example of when Ord
is required is when storing values in a BTreeSet<T>
,
a data structure that stores data based on the sort order of the values.
The Clone
trait allows you to explicitly create a deep copy of a value, and
the duplication process might involve running arbitrary code and copying heap
data. See “Variables and Data Interacting with Clone” on page XX for more
information on Clone
.
Deriving Clone
implements the clone
method, which when implemented for the
whole type, calls clone
on each of the parts of the type. This means all the
fields or values in the type must also implement Clone
to derive Clone
.
An example of when Clone
is required is when calling the to_vec
method on a
slice. The slice doesn’t own the type instances it contains, but the vector
returned from to_vec
will need to own its instances, so to_vec
calls
clone
on each item. Thus the type stored in the slice must implement Clone
.
The Copy
trait allows you to duplicate a value by only copying bits stored on
the stack; no arbitrary code is necessary. See “Stack-Only Data: Copy” on page
XX for more information on Copy
.
The Copy
trait doesn’t define any methods to prevent programmers from
overloading those methods and violating the assumption that no arbitrary code
is being run. That way, all programmers can assume that copying a value will be
very fast.
You can derive Copy
on any type whose parts all implement Copy
. A type that
implements Copy
must also implement Clone
because a type that implements
Copy
has a trivial implementation of Clone
that performs the same task as
Copy
.
The Copy
trait is rarely required; types that implement Copy
have
optimizations available, meaning you don’t have to call clone
, which makes
the code more concise.
Everything possible with Copy
you can also accomplish with Clone
, but the
code might be slower or have to use clone
in places.
The Hash
trait allows you to take an instance of a type of arbitrary size and
map that instance to a value of fixed size using a hash function. Deriving
Hash
implements the hash
method. The derived implementation of the hash
method combines the result of calling hash
on each of the parts of the type,
meaning all fields or values must also implement Hash
to derive Hash
.
An example of when Hash
is required is in storing keys in a HashMap<K, V>
to store data efficiently.
The Default
trait allows you to create a default value for a type. Deriving
Default
implements the default
function. The derived implementation of the
default
function calls the default
function on each part of the type,
meaning all fields or values in the type must also implement Default
to
derive Default
.
The Default::default
function is commonly used in combination with the struct
update syntax discussed in “Creating Instances from Other Instances with Struct
Update Syntax” on page XX. You can customize a few fields of a struct and then
set and use a default value for the rest of the fields by using
..Default::default()
.
The Default
trait is required when you use the method unwrap_or_default
on
Option<T>
instances, for example. If the Option<T>
is None
, the method
unwrap_or_default
will return the result of Default::default
for the type
T
stored in the Option<T>
.
In this appendix, we talk about some useful development tools that the Rust project provides. We’ll look at automatic formatting, quick ways to apply warning fixes, a linter, and integrating with IDEs.
The rustfmt
tool reformats your code according to the community code style.
Many collaborative projects use rustfmt
to prevent arguments about which
style to use when writing Rust: everyone formats their code using the tool.
Rust installations include rustfmt
by default, so you should already have the
programs rustfmt
and cargo-fmt
on your system. These two commands are
analagous to rustc
and cargo
in that rustfmt
allows finer-grained control
and cargo-fmt
understands conventions of a project that uses Cargo. To format
any Cargo project, enter the following:
$ cargo fmt
Running this command reformats all the Rust code in the current crate. This
should only change the code style, not the code semantics. For more information
on rustfmt
, see its documentation at https://github.com/rust-lang/rustfmt.
The rustfix
tool is included with Rust installations and can automatically
fix compiler warnings that have a clear way to correct the problem that’s
likely what you want. You’ve probably seen compiler warnings before. For
example, consider this code:
Filename: src/main.rs
fn do_something() {}
fn main() {
for i in 0..100 {
do_something();
}
}
Here, we’re calling the do_something
function 100 times, but we never use the
variable i
in the body of the for
loop. Rust warns us about that:
$ cargo build
Compiling myprogram v0.1.0 (file:///projects/myprogram)
warning: unused variable: `i`
--> src/main.rs:4:9
|
4 | for i in 0..100 {
| ^ help: consider using `_i` instead
|
= note: #[warn(unused_variables)] on by default
Finished dev [unoptimized + debuginfo] target(s) in 0.50s
The warning suggests that we use _i
as a name instead: the underscore
indicates that we intend for this variable to be unused. We can automatically
apply that suggestion using the rustfix
tool by running the command cargo fix
:
$ cargo fix
Checking myprogram v0.1.0 (file:///projects/myprogram)
Fixing src/main.rs (1 fix)
Finished dev [unoptimized + debuginfo] target(s) in 0.59s
When we look at src/main.rs again, we’ll see that cargo fix
has changed the
code:
Filename: src/main.rs
fn do_something() {}
fn main() {
for _i in 0..100 {
do_something();
}
}
The for
loop variable is now named _i
, and the warning no longer appears.
You can also use the cargo fix
command to transition your code between
different Rust editions. Editions are covered in Appendix E.
The Clippy tool is a collection of lints to analyze your code so you can catch common mistakes and improve your Rust code. Clippy is included with standard Rust installations.
To run Clippy’s lints on any Cargo project, enter the following:
$ cargo clippy
For example, say you write a program that uses an approximation of a mathematical constant, such as pi, as this program does:
Filename: src/main.rs
fn main() {
let x = 3.1415;
let r = 8.0;
println!("the area of the circle is {}", x * r * r);
}
Running cargo clippy
on this project results in this error:
error: approximate value of `f{32, 64}::consts::PI` found
--> src/main.rs:2:13
|
2 | let x = 3.1415;
| ^^^^^^
|
= note: `#[deny(clippy::approx_constant)]` on by default
= help: consider using the constant directly
= help: for further information visit https://rust-lang.github.io/rust-
clippy/master/index.html#approx_constant
This error lets you know that Rust already has a more precise PI
constant
defined, and that your program would be more correct if you used the constant
instead. You would then change your code to use the PI
constant.
The following code doesn’t result in any errors or warnings from Clippy:
Filename: src/main.rs
fn main() {
let x = std::f64::consts::PI;
let r = 8.0;
println!("the area of the circle is {}", x * r * r);
}
For more information on Clippy, see its documentation at https://github.com/rust-lang/rust-clippy*.*
To help with IDE integration, the Rust community recommends using
rust-analyzer
. This tool is a set of compiler-centric utilities that speak
Language Server Protocol, which is a specification for IDEs and programming
languages to communicate with each other. Different clients can use
rust-analyzer
, such as the Rust analyzer plug-in for Visual Studio Code at
https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer.
Visit the rust-analyzer
project’s home page at
https://rust-analyzer.github.io for installation instructions, then install
the language server support in your particular IDE. Your IDE will gain
capabilities such as autocompletion, jump to definition, and inline errors
In Chapter 1, you saw that cargo new
adds a bit of metadata to your
Cargo.toml file about an edition. This appendix talks about what that means!
The Rust language and compiler have a six-week release cycle, meaning users get a constant stream of new features. Other programming languages release larger changes less often; Rust releases smaller updates more frequently. After a while, all of these tiny changes add up. But from release to release, it can be difficult to look back and say, “Wow, between Rust 1.10 and Rust 1.31, Rust has changed a lot!”
Every two or three years, the Rust team produces a new Rust edition. Each edition brings together the features that have landed into a clear package with fully updated documentation and tooling. New editions ship as part of the usual six-week release process.
Editions serve different purposes for different people:
- For active Rust users, a new edition brings together incremental changes into an easy-to-understand package.
- For non-users, a new edition signals that some major advancements have landed, which might make Rust worth another look.
- For those developing Rust, a new edition provides a rallying point for the project as a whole.
At the time of this writing, three Rust editions are available: Rust 2015, Rust 2018, and Rust 2021. This book is written using Rust 2021 edition idioms.
The edition
key in Cargo.toml indicates which edition the compiler should
use for your code. If the key doesn’t exist, Rust uses 2015
as the edition
value for backward compatibility reasons.
Each project can opt in to an edition other than the default 2015 edition. Editions can contain incompatible changes, such as including a new keyword that conflicts with identifiers in code. However, unless you opt in to those changes, your code will continue to compile even as you upgrade the Rust compiler version you use.
All Rust compiler versions support any edition that existed prior to that compiler’s release, and they can link crates of any supported editions together. Edition changes only affect the way the compiler initially parses code. Therefore, if you’re using Rust 2015 and one of your dependencies uses Rust 2018, your project will compile and be able to use that dependency. The opposite situation, where your project uses Rust 2018 and a dependency uses Rust 2015, works as well.
To be clear: most features will be available on all editions. Developers using any Rust edition will continue to see improvements as new stable releases are made. However, in some cases, mainly when new keywords are added, some new features might only be available in later editions. You will need to switch editions if you want to take advantage of such features.
For more details, The Edition Guide at
https://doc.rust-lang.org/stable/edition-guide is a complete book about
editions that enumerates the differences between editions and explains how to
automatically upgrade your code to a new edition via cargo fix
.