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:
| |
The panic message looks like this:
| |
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:
| |
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():
| |
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.