
Rust 1.96.0 shipped on May 28 and closed two long-standing ergonomics gaps: range types that couldn’t be Copy, and an assertion macro that actually tells you what failed. Neither is as flashy as async improvements or the borrow checker, but both will show up in real codebases within hours of an upgrade.
Copy Range Types: The Workaround Era Is Over
Here’s a thing Rust developers have been doing for years:
struct Span {
start: usize,
end: usize, // can't use Range<usize> — it's not Copy
}
If you’ve written a parser, a compiler pass, a syntax highlighter, or anything that tracks byte or character positions, you’ve been there. core::ops::Range<T> implements Iterator directly, and it’s unsafe to make an Iterator also Copy — iterating mutates state, so a silently copied iterator would produce duplicate or skipped elements. The type system said no, and developers stored (usize, usize) tuples and moved on.
Rust 1.96 ships the fix from RFC 3550: new range types under core::range that implement IntoIterator instead of Iterator. The range itself is now just data — Copy, lightweight, storable in any struct — and converts into an iterator on demand.
use core::range::Range;
#[derive(Copy, Clone)]
struct Span {
range: Range<usize>,
}
fn excerpt(span: Span, src: &str) -> &str {
&src[span.range] // no .clone(), no tuple unpacking
}
The three new stable types are core::range::Range, core::range::RangeFrom, and core::range::RangeInclusive. The old types aren’t going anywhere — they live on under core::range::legacy::* — so this is purely additive. RFC 3550 took roughly two years from proposal to stabilization. That’s slower than it should have been for something this widely requested, but at least the answer isn’t “just clone it” anymore.
assert_matches!: Test Failures That Actually Help
The second fix is smaller but immediately practical. assert_matches! and debug_assert_matches! are now stable after years on nightly.
The difference from assert!(matches!(val, pat)) is information density. When assert!(matches!()) fails, your output says:
thread 'test_parse_response' panicked at 'assertion failed'
When assert_matches! fails, you get the actual value printed:
thread 'test_parse_response' panicked at 'assertion `left matches right` failed
left: Err(Timeout { after: 30s, retries: 3 })'
The macros are not in the standard prelude — a deliberate choice to avoid breaking popular third-party crates with the same name. Import manually:
use std::assert_matches::assert_matches;
// Range pattern
assert_matches!(response.status, 200..=299);
// Enum variant with guard
assert_matches!(result, Ok(n) if n > 0);
// debug_assert_matches strips out in release builds
use std::assert_matches::debug_assert_matches;
debug_assert_matches!(cache.get(&key), Some(_));
The third-party assert_matches crate had millions of downloads precisely because this was missing from std. Now it belongs where it should have been from the start.
Cargo CVE Fixes: Low Urgency, Still Worth Upgrading
Two Cargo vulnerabilities are fixed in 1.96. CVE-2026-5223 (medium severity) let a malicious crate in a third-party registry overwrite another crate’s cached source via symlinks in its tarball. CVE-2026-5222 (low severity) is a URL normalization issue for sparse-protocol registries under shared domain hosting. Neither affects crates.io users — crates.io has never allowed symlinks in uploads and doesn’t use shared-domain hosting.
If you use a private or third-party registry, upgrade now. Everyone else: upgrade because 1.96 is good, not because these CVEs are urgent.
How to Upgrade
rustup update stable
rustc --version # should show rustc 1.96.0
If you target WebAssembly and haven’t migrated from the old --allow-undefined behavior yet, that change also shipped in 1.96 and requires a migration step. The full release notes are at the official Rust blog.













