EthicsComputerCourses
Join training +91 90532 10052

Editions & What Changed in 2024

Rust · Lesson 36 of 43 Advanced 8 min read Updated

Rust promises that code which compiles today will keep compiling with future compilers. Yet languages need to fix old mistakes, add keywords and tidy rules, which could break existing code. Rust solves this conflict with editions: opt-in sets of changes that each project chooses for itself.

In this lesson you will learn how editions work, what each edition changed (so you can read older code), what the 2024 edition brought, how to migrate a project, and which features arrived in the regular releases since then, up to Rust 1.99.

How editions work

The edition is one line in Cargo.toml:

TOML
[package]
name = "marks_report"
version = "0.1.0"
edition = "2024"
rust-version = "1.85"
  • New projects created with cargo new use the newest edition. A project without an edition key is treated as 2015.
  • Every Rust compiler supports every edition. Rust 1.99 compiles 2015 code exactly as it always did.
  • Crates of different editions work together in one program. Your 2024 crate can depend on a library written for 2018, so the ecosystem never splits.
  • Editions change only what code looks like to the compiler (keywords, some defaults, lints). After compilation, all editions produce the same kind of program.
  • rust-version (optional) states the oldest compiler your project supports.

A new edition has come out roughly every three years:

EditionArrived withMain changes
2015Rust 1.0 (May 2015)The original language
2018Rust 1.31 (December 2018)New module paths (crate::, no extern crate), dyn Trait, async and await reserved as keywords, non-lexical lifetimes
2021Rust 1.56 (October 2021)Closures capture individual fields, array.into_iter() yields values, TryFrom/TryInto/FromIterator in the prelude, Cargo feature resolver 2 by default
2024Rust 1.85 (February 2025)Changes listed below; the biggest edition so far

Many features are not tied to editions at all. ?, async fn, let ... else, generic associated types and most library additions work in every edition, because they did not break existing code. Only changes that could break code wait for a new edition.

What the 2024 edition changed

Language

  • impl Trait captures lifetimes. A return type -> impl Trait now automatically captures all generic parameters and lifetimes in scope. When you need to capture less, the precise-capturing syntax use<...> lists exactly what is captured.
  • Shorter temporary lifetimes. Temporaries in the condition of an if let, and in the final expression of a block, are now dropped earlier. This removes a classic source of deadlocks and "already borrowed" panics.
  • Unsafe made explicit. extern blocks must be written unsafe extern, attributes such as no_mangle become #[unsafe(no_mangle)], unsafe operations inside an unsafe fn need their own unsafe { } block, and references to static mut are denied (see Unsafe Rust).
  • Never type fallback changed, and some match-pattern combinations were reserved for future improvements to "match ergonomics".
  • Macros: the expr fragment specifier now also matches const { ... } blocks and _; a missing fragment specifier is now an error.
  • Reserved syntax: gen is a keyword (for future generator blocks), and guarded strings such as #"text"# are reserved.
  • Let chains (if let ... && condition), stabilised later in Rust 1.88, are available only in the 2024 edition.

Standard library

  • Future and IntoFuture are in the prelude.
  • Box<[T]> implements IntoIterator by value.
  • std::env::set_var and remove_var are now unsafe to call, because changing environment variables while other threads read them is a data race.

Cargo, Rustdoc and Rustfmt

  • The dependency resolver is rust-version aware (resolver 3): it prefers dependency versions that support your rust-version.
  • Old spellings in Cargo.toml, such as dev_dependencies with an underscore, are no longer accepted; use dev-dependencies.
  • Doc tests are combined into one program per crate, which runs much faster.
  • Rustfmt has a separate style edition, and sorts names containing numbers in a more natural "version" order.

Two changes you can see in code

The first example shows use<> asking a returned iterator to capture nothing. Without it, in 2024 the iterator would be treated as borrowing names, and clearing the list would be rejected:

Rust
fn lengths(names: &[String]) -> impl Iterator<Item = usize> + use<> {
    let v: Vec<usize> = names.iter().map(|n| n.len()).collect();
    v.into_iter()
}

fn main() {
    let mut names = vec![String::from("Isha"), String::from("Rohan")];
    let lens = lengths(&names);
    names.clear(); // allowed: `lens` does not borrow `names`
    println!("{:?}", lens.collect::<Vec<_>>());
}
Output
[4, 5]

The second shows the new if let temporary scope. The cache.borrow() guard is now dropped before the else block runs, so borrowing mutably there is fine. Under the 2021 edition the guard lived until the end of the whole if let ... else, and this program would panic with an "already borrowed" error:

Rust
use std::cell::RefCell;

fn main() {
    let cache: RefCell<Option<u32>> = RefCell::new(None);

    if let Some(v) = *cache.borrow() {
        println!("Cached value: {v}");
    } else {
        *cache.borrow_mut() = Some(42);
        println!("Stored 42");
    }
    println!("{:?}", cache.borrow());
}
Output
Stored 42
Some(42)

Migrating a project to 2024

Cargo can rewrite most code automatically. The usual steps are:

Terminal
rustup update stable
cargo fix --edition        # rewrites code so it works in both editions
# now change edition = "2021" to edition = "2024" in Cargo.toml
cargo build
cargo test

cargo fix --edition is deliberately cautious: it keeps the old behaviour exactly, sometimes by making code more verbose (for example adding use<..> bounds or extra blocks). After the switch, review its changes and simplify where the new behaviour is what you wanted. In a workspace with many crates, you can migrate one crate at a time.

Release highlights since the 2024 edition

Between editions, a new stable Rust appears every six weeks. A selection of what arrived after Rust 1.85:

VersionDateHighlights
1.86April 2025Trait upcasting (&dyn Child to &dyn Parent), get_disjoint_mut on slices and HashMap
1.87May 2025Vec::extract_if, anonymous pipes in std::io::pipe
1.88June 2025Let chains (2024 edition), naked functions, Cargo cleans its cache automatically
1.90September 2025LLD is the default linker on x86-64 Linux, cargo publish --workspace
1.94March 2026array_windows on slices, include in Cargo config, TOML 1.1 in manifests
1.95April 2026if let guards in match arms, the cfg_select! macro
1.96May 2026New range types in core::range, the assert_matches! macro
1.97July 2026CARGO_BUILD_WARNINGS to allow or deny warnings, v0 symbol mangling by default
1.98August 2026Algebraic float methods such as algebraic_add, format_into for integers
1.99October 2026Defining C-variadic functions in Rust, Vec::into_parts and Vec::from_parts

The official release notes on the Rust blog list every change. A good habit is to run rustup update when a release appears and skim its announcement.

Common mistakes

  • Changing the edition number before running cargo fix --edition. Run the fix first, while the old edition is still set, then change the number.
  • Thinking an old edition means an old compiler. You can use Rust 1.99 with edition 2018; you simply do not get the 2024 rules.
  • Expecting every new feature to need a new edition. Most features arrive in normal six-weekly releases for all editions.
  • Assuming cargo fix output is the ideal style. It preserves behaviour; tidy it afterwards.

Key points

  • An edition (2015, 2018, 2021, 2024) is chosen per crate in Cargo.toml; crates of different editions work together.
  • Editions contain only changes that could break code; most features ship in regular releases every six weeks.
  • The 2024 edition changed impl Trait lifetime capture, temporary scopes, unsafe rules, the prelude and Cargo's resolver, and enables let chains.
  • Migrate with cargo fix --edition, then set edition = "2024" and build and test.
  • Releases after 1.85 added features such as trait upcasting, let chains, if let guards and assert_matches!; Rust 1.99 is the version this course teaches.

Exercise

Look at this 2015-style snippet and rewrite it in modern 2024-edition Rust: extern crate serde;, a function fn show(item: &Display) { println!("{}", item); }, and a nested if let Some(n) = maybe { if n > 10 { ... } }. Then list the three commands you would run to migrate an old 2021 project to 2024.

Show solution
Rust
use std::fmt::Display;

fn show(item: &dyn Display) {
    println!("{item}");
}

fn main() {
    show(&"Kochi");
    let maybe: Option<u32> = Some(25);
    if let Some(n) = maybe
        && n > 10
    {
        println!("Big number: {n}");
    }
}
Output
Kochi
Big number: 25

extern crate is no longer needed: dependencies listed in Cargo.toml are available by name. Trait objects need dyn, and the nested if let becomes a let chain. To migrate: cargo fix --edition, then edit Cargo.toml to edition = "2024", then cargo build and cargo test.

Quick Quiz

1. Can a crate using the 2024 edition depend on a library written for the 2018 edition?

2. Which command rewrites code so it can move to a new edition?

3. Which feature works only in the 2024 edition?

4. What does the use<> in -> impl Iterator<Item = usize> + use<> mean?

Learn Rust with a mentor

Free lessons get you started. Our Programming & Software Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call