Rust-1.96

#234 Jul 2026

234. core::range::Range — A Range That's Copy, So You Can Store and Reuse It

The classic 0..3 isn’t Copy — it is the iterator, so it gets consumed and can’t live in a Copy struct. Rust 1.96 stabilises core::range::Range, which is Copy and iterates through IntoIterator instead.

Why the old Range fights you

std::ops::Range implements Iterator directly. That’s convenient in a for loop, but it means the range is mutable iterator state, so it can’t be Copy:

1
2
3
let r = 0..3;
let total: i32 = r.sum();   // moves r
// let n = r.count();       // error: use of moved value: r

It also can’t sit inside a Copy type, which is annoying when you want a lightweight span:

1
2
#[derive(Clone, Copy)]        // error: the trait Copy is not
struct Span(std::ops::Range<usize>);  // implemented for Range<usize>

The new core::range::Range is Copy

Stabilised in 1.96, core::range::Range implements IntoIterator rather than Iterator, which frees it to be Copy. Convert a literal range into it with .into():

1
2
3
4
5
6
7
use core::range::Range;

let r: Range<usize> = (0..3).into();
let total: usize = r.into_iter().sum();   // r is Copy, not moved
let count = r.into_iter().count();        // still usable
assert_eq!(total, 3);
assert_eq!(count, 3);

Now it fits in a Copy struct

Because it’s Copy, you can store a range as a cheap, copyable span and still index slices with it directly:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
use core::range::Range;

#[derive(Clone, Copy)]
struct Span(Range<usize>);

impl Span {
    fn of(self, s: &str) -> &str { &s[self.0] }
}

let span = Span((2..5).into());
let a = span;                 // copies, no move
assert_eq!(a.of("hello world"), "llo");
assert_eq!(span.of("hello world"), "llo");   // span still valid

No more splitting a range into separate start / end fields just to keep a struct Copy.

Heads up

0..3 syntax still produces the legacy std::ops::Range for now — the new types come in via .into() or by naming core::range::Range. A future edition will switch the syntax over. For public APIs that should accept either, take impl RangeBounds<usize>.

Stabilised in Rust 1.96 (May 2026).

171. assert_matches! — A Test Failure That Actually Tells You What Went Wrong

assert!(matches!(x, Foo::Bar)) panics with assertion failed: matches!(x, Foo::Bar) and zero hint about what x actually was. Rust 1.96 stabilises assert_matches!, which prints the offending value for you.

The old way leaves you guessing

The classic assert!(matches!(...)) combo has been in tests forever, but its failure message is useless:

1
2
3
4
5
6
#[derive(Debug)]
enum Status { Ok, Pending, Failed(u32) }

let s = Status::Failed(42);
assert!(matches!(s, Status::Ok));
// panic: assertion failed: matches!(s, Status :: Ok)

When this fires in CI, you get the pattern back but not the value. Was it Pending? Failed? With what code? You either rerun with dbg! or eyeball the test setup.

assert_matches! includes the value

assert_matches! lives in core (and std) as of 1.96. It checks the same way, but on failure it prints the Debug representation of what you handed it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
use std::assert_matches::assert_matches;

#[derive(Debug)]
enum Status { Ok, Pending, Failed(u32) }

let s = Status::Failed(42);
assert_matches!(s, Status::Ok);
// panic: assertion `left matches right` failed
//   left: Failed(42)
//  right: Status::Ok

Same one-liner, real diagnostic. No extra dbg!, no rerun.

Pattern guards still work

Because it’s a real pattern position, you get bindings and guards too — useful when you want “some variant with a value in a range”:

1
2
3
4
use std::assert_matches::assert_matches;

let n: Result<i32, &str> = Ok(7);
assert_matches!(n, Ok(x) if x > 0);

The guard is checked just like in a match arm, and the panic message still shows you the value if it fails.

Heads up: not in the prelude

Unlike assert! and assert_eq!, assert_matches! is not in the prelude — too many third-party crates (notably the assert_matches crate) already export the same name. Import it explicitly:

1
2
3
use std::assert_matches::assert_matches;
// or, in no_std:
// use core::assert_matches::assert_matches;

There’s also debug_assert_matches! for the same trick that compiles away in release builds.

Stabilised in Rust 1.96 (May 2026). Delete one third-party dependency from your dev-dependencies today.