Testing

172. #[track_caller] — Point the Panic at the Caller, Not Your Helper

You wrap an assert in a helper to clean up your tests. Now every failure points at the helper’s source line instead of the test that called it. #[track_caller] fixes that with a single line of code.

The problem: panics blame the helper

Say you’ve factored out a custom check used across many tests:

1
2
3
4
5
6
7
8
fn assert_positive(x: i32) {
    assert!(x > 0, "expected positive, got {x}");
}

#[test]
fn it_works() {
    assert_positive(-3); // panics
}

The panic message looks like this:

1
2
thread 'it_works' panicked at src/lib.rs:2:5:
expected positive, got -3

src/lib.rs:2 is the line inside assert_positive. Every test that uses this helper points at the same spot. Useless.

The fix: one attribute

Put #[track_caller] on the helper and the reported location becomes whichever call site invoked it:

1
2
3
4
5
6
7
8
9
#[track_caller]
fn assert_positive(x: i32) {
    assert!(x > 0, "expected positive, got {x}");
}

#[test]
fn it_works() {
    assert_positive(-3); // panics, blames THIS line
}

Now the panic points at the test’s call, exactly like a built-in assert! does. That’s because assert!, unwrap, expect, Vec::index, and friends are all themselves #[track_caller].

How it works

The attribute makes the compiler thread the caller’s Location through the function. You can grab it explicitly with core::panic::Location::caller():

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
use std::panic::Location;

#[track_caller]
fn where_am_i() -> &'static Location<'static> {
    Location::caller()
}

fn main() {
    let loc = where_am_i();
    assert_eq!(loc.line(), 11); // the call site, not the fn body
}

The attribute propagates through wrappers — mark every layer between the panic and the public API, otherwise the chain breaks at the first un-annotated function and the location resets to that frame.

When to reach for it

Any time you wrap panic!, assert!, unwrap, or expect behind a helper that callers will treat as a primitive: test assertions, domain-specific unwraps, invariant checks. The cost is zero at runtime in optimized builds — the location is baked in at compile time.

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.