Jacar mascot — reading along A laptop whose eyes follow your cursor while you read.
Desarrollo de Software

Rust Edition 2024: what really changes day to day

Rust Edition 2024: what really changes day to day

Actualizado: 2026-05-03

Rust Edition 2024 came out stable on February 20 alongside Rust 1.85, and seven weeks later there’s enough data to talk about what it brings in practice. Rust editions are a curious mechanism: they don’t break compatibility, but they allow changing defaults that would otherwise break existing code. A crate with edition = "2024" compiles against the same compiler as one with edition = "2021", and the two can depend on each other. What changes is how new code is written and some behaviors that adjust language details.

This post is a practical analysis of the changes that most affect daily work, with emphasis on what actually matters for those already using Rust. For the context of Rust’s role in systems beyond userspace, the analysis of Rust in the Linux kernel illustrates the language’s maturity trajectory.

Key takeaways

  • Migration with cargo fix --edition is the recommended path; it resolves most changes automatically.
  • Granular capture in closures (use<T> in Return Position Impl Trait) eliminates unnecessary lifetime captures.
  • The 2024 prelude adds Future, IntoFuture, AsyncFn*, LazyCell, and LazyLock.
  • unsafe in extern blocks is now mandatory; cargo fix migrates automatically.
  • Closures in async fn no longer require explicit move in most cases.

How Rust editions work

Editions are Rust’s way of evolving the language without breaking existing code. Rust 1.0 introduced the stability promise: code that compiles today will keep compiling in future versions. Editions are the escape valve for changes that would require breaking that promise if applied globally.

When a crate declares edition = "2024" in Cargo.toml, it opts into that edition’s semantics. Crates with edition = "2021" in the same workspace keep compiling with the previous semantics. The compiler knows each crate’s edition and applies the corresponding rules. There’s never cross-crate breakage: a 2024 crate can depend on a 2021 crate and vice versa.

Migration between editions is assisted by cargo fix --edition, which automatically rewrites code that needs to change. Most real projects migrate from 2021 to 2024 with this command and without significant manual intervention, except for cases with unusual closure capture patterns or complex extern blocks.

Granular capture in closures: the most relevant change

The change with the most impact on real code is the refinement of variable capture in closures and in Return Position Impl Trait (RPIT).

In Rust 2021, when a closure captured something implementing Future, it sometimes captured more than necessary, forcing more restrictive lifetimes than the programmer intended. In 2024, capture is more granular: the compiler captures exactly what’s used, not the whole object if only a field is needed.

The practical consequence is that async code that in 2021 required workarounds to satisfy the borrow checker (like explicitly moving data before the async call, or using Arc where it wasn’t semantically necessary) in 2024 simply works. The number of cases where async move {} was mandatory drops significantly.

The complement of this is the use<T> syntax in RPIT, which lets you explicitly declare which lifetimes and generic types an opaque return type captures. This eliminates compiler ambiguity about what’s captured, which was a frequent source of hard-to-debug errors in code with intertwined generics and lifetimes.

The expanded 2024 prelude

The prelude is the set of names Rust automatically imports in every module without an explicit use. In 2024, several async-related types and traits are added:

  • Future and IntoFuture.
  • AsyncFn, AsyncFnMut, AsyncFnOnce.
  • LazyCell and LazyLock (thread-safe lazy initialization).

The most immediate impact is that async code no longer needs to explicitly import Future in every file working with it. LazyCell and LazyLock available in the prelude reduce the boilerplate of lazy initialization, which was a common but verbose pattern with external once_cell.

There’s an important nuance: if a project had identifiers with those same names, the prelude addition can cause name collisions that cargo fix doesn’t always resolve automatically. The scenario is rare but worth checking.

More explicit unsafe in extern blocks

In Rust 2024, extern "C" {} blocks require the explicit unsafe keyword:

rust
// Rust 2021:
extern "C" {
    fn puts(s: *const u8) -> i32;
}

// Rust 2024:
unsafe extern "C" {
    fn puts(s: *const u8) -> i32;
}

The change reinforces signaling that declarations in extern imply code outside Rust’s borrow checker’s control. cargo fix --edition migrates this automatically, so in practice it requires no manual work in most projects.

There’s a useful counterpart: functions inside unsafe extern blocks that the programmer can guarantee are safe to call can be marked safe:

rust
unsafe extern "C" {
    safe fn abs(input: i32) -> i32;  // can be called without unsafe
    unsafe fn gets(s: *mut u8);      // still requires unsafe at the call site
}

This improves FFI binding ergonomics without losing the signal of what’s truly unsafe.

Migration in practice

The migration process for a real project:

  1. Update the toolchain to 1.85 or higher: rustup update stable.
  2. Run cargo fix --edition in the workspace.
  3. Change edition = "2024" in the affected Cargo.toml files.
  4. Run cargo test and cargo clippy and address warnings.
  5. Manually review cases cargo fix couldn’t migrate automatically.

In projects with lots of FFI code or unusual closure patterns, step 5 may require a few hours. In typical application or library projects without FFI, migration usually completes in under thirty minutes.

My read

Rust Edition 2024 is an ergonomics edition, not a headline one. There are no spectacular new features; there are refinements eliminating real friction in common code patterns. Granular closure capture and the async prelude are the changes most appreciated in daily work with async code, which is half the Rust written today.

The migration is straightforward enough that there’s no reason not to do it in active projects already using edition 2021. Risk is low, the tool does most of the work, and benefits are immediate. The only real blocker is if the project maintains compatibility with Rust versions before 1.85, in which case you wait or do the migration conditionally.

Was this useful?
[Total: 13 · Average: 4.5]

Written by

CEO - Jacar Systems

Passionate about technology, cloud infrastructure and artificial intelligence. Writes about DevOps, AI, platforms and software from Madrid.