rustc --edition: compile with Rust 2015, 2018, 2021 or 2024
- tool
- Rust
- command
- rustc
- flag
- --edition <2015|2018|2021|2024>
- default
- 2015 for rustc; cargo new writes the latest edition into Cargo.toml
rustc --edition 2021 main.rs compiles the file with the rules of the Rust 2021 edition. Editions are opt-in language versions: each one can reserve new keywords and change a few defaults, while crates of different editions still link together. Plain rustc defaults to the 2015 edition, whereas Cargo passes the edition from Cargo.toml, so the same code can compile under Cargo and fail with rustc file.rs, or the other way round.
A keyword that depends on the edition
async became a keyword in 2018 and gen is reserved in 2024:
cat > old.rs <<'EOF'
fn main() {
let async = 1;
println!("async = {}", async);
}
EOF
cat > new.rs <<'EOF'
fn main() {
let gen = 5;
println!("gen = {gen}");
}
EOF
echo "== old.rs, default edition"
rustc old.rs && ./old
echo "== old.rs, --edition 2018"
rustc --edition 2018 old.rs; echo "exit: $?"
echo "== new.rs, --edition 2021"
rustc --edition 2021 new.rs && ./new
echo "== new.rs, --edition 2024"
rustc --edition 2024 new.rs 2>&1 | sed -n "1,11p"; echo "exit: ${PIPESTATUS[0]}"
echo "== invalid value"
rustc --edition 2027 new.rs; echo "exit: $?"Output
== old.rs, default edition
async = 1
== old.rs, --edition 2018
error: expected identifier, found keyword `async`
--> old.rs:2:9
|
2 | let async = 1;
| ^^^^^ expected identifier, found keyword
|
help: escape `async` to use it as an identifier
|
2 | let r#async = 1;
| ++
error: expected one of `move`, `use`, `{`, `|`, or `||`, found `<eof>`
--> old.rs:3:28
|
3 | println!("async = {}", async);
| ^^^^^ expected one of `move`, `use`, `{`, `|`, or `||`
error: aborting due to 2 previous errors
exit: 1
== new.rs, --edition 2021
gen = 5
== new.rs, --edition 2024
error: expected identifier, found reserved keyword `gen`
--> new.rs:2:9
|
2 | let gen = 5;
| ^^^ expected identifier, found reserved keyword
|
help: escape `gen` to use it as an identifier
|
2 | let r#gen = 5;
| ++
exit: 1
== invalid value
error: argument for `--edition` must be one of: <2015|2018|2021|2024|future>. (instead was `2027`)
exit: 1The 2015 code that uses async as a variable name builds with the default edition and fails from 2018 on; rustc suggests the raw identifier r#async. In the same way gen is fine in 2021 and an error in 2024.
Editions at a glance
| Edition | Stable since | Examples of what changed |
|---|---|---|
| 2015 | Rust 1.0 | Default for rustc; extern crate needed for dependencies |
| 2018 | Rust 1.31 | async, await, dyn and try reserved; paths start with crate:: or a crate name; no extern crate |
| 2021 | Rust 1.56 | Disjoint closure captures, IntoIterator for arrays by value, new prelude (TryFrom, TryInto), panic! always treats its first argument as a format string |
| 2024 | Rust 1.85 | gen reserved, unsafe extern blocks, RPIT lifetime capture rules, tail expression temporary scope |
The full list for each edition is in the Rust Edition Guide. rustc --edition future exists for compiler testing and requires a nightly toolchain.
rustc versus Cargo
[package]
name = "app"
version = "0.1.0"
edition = "2024"Cargo turns the edition field into --edition 2024 for every rustc call of that crate. When you compile a single file outside Cargo (see compile a single file with rustc), pass the edition yourself, otherwise you get 2015 behaviour: use paths resolve differently, dyn is a warning instead of required, and keywords such as async are ordinary identifiers. A project made with cargo new already has the latest edition set.
Pitfalls
- An edition does not choose the compiler version:
--edition 2024with a rustc older than 1.85 fails with an error listing the editions it knows, as the invalid value above does. - Mixed editions in one workspace are normal; set
editionin each member'sCargo.tomlor once in[workspace.package]. rustfmthas its owneditionsetting; if formatting rejects valid code, pass the same edition torustfmt --edition.
FAQ
What edition does rustc use without --edition?
2015. Plain rustc keeps the oldest edition for compatibility, while cargo new writes the newest edition into Cargo.toml. That is why a file that builds with cargo can fail with rustc file.rs.
Can crates of different editions be used together?
Yes. The edition is chosen per crate, and a 2024 crate can depend on 2015 or 2018 crates. Raw identifiers such as r#async let code in a newer edition call items whose names became keywords.
How do I move a project to a newer edition?
Run cargo fix --edition, which applies the automatic migrations while the old edition is still set, then change the edition field in Cargo.toml and build again. The Edition Guide lists the changes per edition.