Rust 1.96.0 landed this week, and the headline item is a macro that should have been stable years ago. assert_matches! is now a first-class citizen of the standard library. It does exactly what every Rust developer has hand-rolled a dozen times: assert that a pattern matches, and panic with a readable message if it does not.

The macro is the kind of thing that makes you wonder why it took this long. The answer is process. assert_matches! existed in the matches! macro family as an unstable feature since Rust 1.46.0. It took six years, a dedicated tracking issue, and a stabilization report to get it across the line. The companion debug_assert_matches! ships alongside it, meaning debug-build-only pattern assertions are now one import away.

This is Rust’s rhythm now. The language is past its era of big, splashy features. The edition train has left the station. What remains is the slow, deliberate work of filling in the gaps that every practicing Rustacean has learned to work around.

What else is in the box

The release notes are short. That is itself a signal. A language that ships fewer changes per release is a language that has stabilized its core model.

On the language side, the compiler now allows passing expr metavariables to cfg. This is a procedural-macro quality-of-life fix. Before 1.96.0, if a proc macro produced an expression token and you tried to feed it into a cfg invocation, the compiler would reject it. The fix in PR 146961 closes that hole. It matters most for macro authors who generate conditional compilation based on runtime-constructed tokens.

The never-type coercion fix in PR 147834 is the kind of change that will silently fix bugs in code nobody is looking at. Rust’s never type (!) represents computations that never complete. In tuple expressions, the compiler now always coerces never types to the expected type. The previous behavior was inconsistent. Code that compiled on one version might fail on the next. This change makes the coercion uniform.

The ManuallyDrop pattern fix in PR 154891 is a regression repair. Rust 1.94.0 introduced a change that broke the ability to use constants of type ManuallyDrop in match patterns. 1.96.0 restores it. The fix is small. The fact that it needed fixing at all is a reminder that even mature compilers have edge cases.

The infrastructure story

Two compiler-target changes stand out. The riscv64gc-unknown-fuchsia target baseline moves to RVA22 with vector support. Fuchsia is Google’s bet on a Rust-first operating system kernel. Raising the baseline means the Fuchsia team can assume vector instructions on all supported hardware. It is a vote of confidence in the platform’s longevity.

The LoongArch link relaxation feature in PR 153427 is the opposite of glamorous. Link relaxation is a linker optimization that shortens instruction sequences when the target address is within a smaller range. For LoongArch Linux targets, this feature was disabled. Now it is enabled. The result is smaller binaries and faster code on a Chinese CPU architecture that is slowly accumulating Rust support. Loongson, the company behind LoongArch, has been investing in Rust toolchain support for years. This is another brick in that wall.

The library changes that matter

The NonZero integer range iteration in PR 127534 is a usability win that touches more code than it looks like. NonZero<u32>, NonZero<u64>, and their siblings are Rust’s way of representing integers that are guaranteed not to be zero. They are used extensively in collections, allocators, and systems code. Before 1.96.0, you could not iterate over a range of NonZero integers with for x in NonZeroU32::new(1).unwrap()..NonZeroU32::new(10).unwrap(). Now you can. The compiler generates the correct iteration code. It is a small thing. It is also the kind of small thing that, when missing, forces developers to write manual loops and re-introduce the very bugs the type system was supposed to prevent.

The null-pointer refactor in PR 152615 is a documentation and soundness change. The standard library’s definition of “valid for read/write” previously included null pointers as a special case. The new definition excludes null, and adds the exception on individual methods instead. This is a semantic tightening. It makes the contract clearer for unsafe code authors. It does not change behavior for safe code. It does change the mental model for the people writing the raw pointer operations that underpin every Rust program’s memory safety.

What this means for builders

Rust 1.96.0 is not a release that demands an immediate upgrade. It does not contain a new edition, a borrow-checker overhaul, or a trait-system revolution. It is a release that rewards the upgrade with a handful of sharp edges filed down.

The message from the Rust project is consistent. The language is not done. It is in a different phase. The phase where the work is invisible to everyone except the people who write the code. assert_matches! is the visible proof. The never-type coercion fix is the invisible one. Both matter.

The question for teams on older Rust versions is whether to chase the tail. The answer is yes, but not for the headline features. Upgrade for the regression fixes. Upgrade for the NonZero iteration. Upgrade because the cost of staying on an old compiler compounds in ways that are hard to measure until you hit a bug that was fixed two releases ago.

Rust 1.96.0 is a small release. That is not a criticism. It is the sound of a language that has stopped breaking things and started polishing them.