Rust 1.96.0, released on May 28 2026, fixes one of the language's oldest ergonomic annoyances: range types can now be Copy. For years, a plain 0..10 could not be freely duplicated the way an integer can, because the range type was also an iterator carrying mutable state. RFC 3550 untangles that by introducing new range types in core::range that implement IntoIterator instead of Iterator, so a range is once again a pure value describing a span. It sounds small. For library authors who have wrestled with splitting ranges into start and end fields for a decade, it is the headline of the year.

  • Copy-able ranges via RFC 3550: new core::range types separate a range as a value from the iterator over it.
  • assert_matches! and debug_assert_matches! are now stable, giving pattern-based assertions with far better failure output.
  • The minimum external LLVM is now 21, so anyone building Rust from source must check their toolchain before upgrading.
  • A WebAssembly linking change can break builds that relied on implicit undefined symbol imports, fixable with an explicit flag or annotation.
Why the old Range could not be Copy The legacy Range is both a value and an iterator with internal state, so copying it would silently duplicate iteration progress. The new core::range types split those roles. LEGACY std::ops::Range Range = value + iterator carries mutable progress Copy would duplicate that state NEW core::range (RFC 3550) Range pure value is Copy Iter holds progress via IntoIterator A type that is both Copy and Iterator is a known footgun: copying it silently forks the iteration. Splitting the roles lets a range be a cheap, duplicable value again. Migration note: 0..10 still makes the old type today. The switch to core::range is planned for the Rust 2027 edition, once edition-aware path resolution lands. genztech.blog
Fig 1 The legacy Range fused a value and an iterator, so it could not be Copy without silently duplicating iteration state. RFC 3550 splits those roles, making the range itself a plain Copy value.

Why could ranges not be Copy before?

Because the original std::ops::Range implements Iterator directly, and a type that is simultaneously Iterator and Copy is a recognized trap. The iterator carries internal mutable state, so giving it Copy semantics would let a copy silently duplicate that state, producing surprising iteration behavior when you least expect it. The Rust team refused to paper over that for years. RFC 3550's fix is structural: the new core::range types implement IntoIterator rather than Iterator, cleanly separating the concept of a range, a pure value describing a span, from the concept of an iterator over that range. With that split, the range value can be Copy without any footgun, and the new RangeInclusive even makes its fields public, unlike the legacy version that had to hide its exhausted-iterator state.

RelatedWindows 11 Tests Cloud Rebuild, a No-USB Recovery Tool

What else is worth an upgrade?

Two things stand out for everyday code. First, assert_matches! and debug_assert_matches! are now stable in core and std. They give you pattern-based assertions with far more useful failure output than the old assert!(matches!(..)) dance, because when the assertion fails the macro prints the Debug representation of the actual value, so you see what you got instead of a bare false. The debug variant is stripped in release builds like debug_assert!, so it costs nothing in optimized code. Second, Cargo now lets a single dependency specify both a git repository and an alternate registry, using the git version in local development and the registry version on publish, which is genuinely useful for teams that maintain forks or internal mirrors.

ChangeRust 1.96 impactWho feels it
Copy ranges (core::range)Store ranges in Copy typesLibrary authors
assert_matches!Better test failure outputEveryone writing tests
Min LLVM 21Toolchain bump requiredSource builders, distros
WASM undefined symbolsPossible link errorsWebAssembly targets
Cargo git + registry depFork-friendly dev flowTeams with mirrors

What could break on upgrade?

Two edges deserve attention. WebAssembly targets that relied on implicit undefined symbol imports will now produce linker errors, which you resolve either by setting RUSTFLAGS to pass the allow-undefined link argument or by annotating the extern block with the wasm_import_module attribute to make intent explicit. The change is a correctness improvement, but it will surprise anyone who leaned on the old implicit behavior. Separately, the minimum external LLVM is now 21, so if you build the compiler from source rather than using rustup, verify your LLVM version first. Two Cargo CVEs were also fixed, one medium-severity issue in tarball extraction with symlinks and one low-severity URL normalization issue, though crates.io users were not affected by either.

When do the new ranges become the default?

Not yet, and that is the honest caveat. Range syntax like 0..10 still produces the old std::ops types in 1.96. The plan is to switch the language to the new core::range types in the Rust 2027 edition, but the edition-aware path resolution in the standard library that would make that seamless does not exist yet. So today you opt into the new types explicitly. The stabilization is the foundation; the ergonomic payoff for casual code lands with the edition.

RelatedDeno 2.9 Ships Desktop Apps and Post-Quantum Crypto

What to watch · Rust 2026 to 2027
  • The 2027 edition migration. The real win is when 0..10 just makes a Copy range. Watch the edition-aware path resolution work that unblocks it.
  • Ecosystem adoption. How quickly popular crates expose the new range types in their APIs decides how visible this is day to day.
  • LLVM 21 fallout. Distro maintainers on older LLVM will feel the minimum bump first. Expect packaging churn.
  • WASM build breakage. The undefined-symbol change is the most likely upgrade surprise for real projects.

Our take

Rust 1.96 is a connoisseur's release: no flashy feature, just the payoff of a decade-long refusal to ship a convenient-but-wrong fix. Copy ranges are the kind of thing only library authors will cheer out loud, but everyone benefits downstream when the crates they depend on get cleaner APIs. The pattern here is very Rust: identify a subtle correctness trap, decline the easy shortcut, and wait years to do it properly with a structural fix that leaves no footgun behind. assert_matches! going stable is the quiet crowd-pleaser that will improve test output across the ecosystem immediately. Upgrade when your LLVM and WASM setup are ready, and note the real ergonomic gift is coming with the 2027 edition.

Primary sources

Original analysis by GenZTech. Figures current as of July 2026. Source: Rust Blog.