Rust 2024 is the largest edition since 2018
New match ergonomics, gen blocks reserved, RPIT lifetime capture changes, and unsafe attributes. Here's what will break.
Rust 1.85 shipped today and with it the Rust 2024 edition — the third edition since 1.0 and the most substantial one. Editions are opt-in per crate and do not split the ecosystem: a 2015 crate and a 2024 crate link together fine. But migrating your own code will surface real changes.
Run cargo fix --edition first. It handles most of it. Here is what it cannot.
the changes that will bite#
RPIT lifetime capture. In edition 2024, -> impl Trait in a free function captures all in-scope lifetime parameters by default, matching what async fn already did. Previously it captured only type parameters, and you had to add a + '_ or a witness parameter to opt in.
This makes the common case work without incantations. It also means some signatures that compiled before now fail borrow checking, because the returned opaque type is now considered to borrow something it did not before. The fix is the new use<..> syntax to state precisely what is captured:
fn parse<'a>(input: &'a str) -> impl Iterator<Item = &'a str> + use<'a> {
input.split(',')
}unsafe attributes. #[no_mangle], #[export_name] and #[link_section] are now written #[unsafe(no_mangle)]. These attributes can violate soundness — no_mangle can collide with a symbol the linker resolves differently — and the language now says so out loud. Mechanical fix, applied by cargo fix.
unsafe extern blocks. Declaring a foreign function is an unsafe assertion about its signature. Now spelled that way.
gen is a reserved keyword. Generator blocks are coming; the keyword is reserved now so it can land without another edition. If you have a variable named gen, rename it. Yes, this affects more code than you would think.
Tail expression temporary scope. Temporaries in the final expression of a block now drop before local variables rather than after. This fixes a class of surprising deadlocks with MutexGuard in a tail position, and changes drop order in ways that could matter if you have Drop impls with side effects.
if let temporary scope. Temporaries in an if let scrutinee now drop at the end of the if let, not at the end of the enclosing statement. Same category: fixes real deadlocks, changes observable drop timing.
Never type fallback. ! now falls back to ! instead of () in more positions. Mostly this makes bad code fail to compile rather than silently doing something odd.
Cargo resolver v3 becomes the default, which is the MSRV-aware resolver from 1.84.
should you migrate#
Yes, but not urgently and not during a crunch. The migration is mostly mechanical; the RPIT changes and the drop-order changes are the two places where you need to actually think.
Do it on a quiet week, run your full test suite, and pay specific attention to anything with a Drop impl that touches shared state. That is where a silently-changed behavior can hide.
the meta-point about editions#
Rust's edition mechanism remains the best answer anyone has shipped to the problem of evolving a language without breaking the world. C++ cannot do this. Python's answer took a decade and cost the community enormously. Rust gets to change syntax and semantics on a six-year cadence, per-crate, with automated migration, and old code keeps compiling forever.
That is a genuinely important piece of language design and it is underrated because it works.
— Dom, February 20, 2025