#[track_caller]
#[track_caller] is a function and method attribute that passes the caller's source location (&'static std::panic::Location<'static>—containing the file path, line number, and column number of the call site) implicitly into the function. It causes both std::panic::Location::caller() and any panic triggered inside the function to report the caller's line of code rather than the internal line inside the helper function.
Why Option::unwrap, Result::expect, and Index [] Use #[track_caller]
Without #[track_caller], calling opt.unwrap() on None would report a panic at line 950 of library/core/src/option.rs—forcing you to run with RUST_BACKTRACE=1 just to see which line of your own code called .unwrap().
Because Option::unwrap is annotated with #[track_caller], the panic message points directly to the line in your function that called .unwrap()!
Without `#[track_caller]`:
main() [src/main.rs:20] ---> assert_non_empty("") [src/helpers.rs:5]
`--> Panic reports `src/helpers.rs:5:9`!
With `#[track_caller]` on `assert_non_empty`:
main() [src/main.rs:20] ---> #[track_caller] assert_non_empty("")
`--> Panic reports `src/main.rs:20:5` (the caller)!
Reading Call-Site Coordinates via Location::caller()
Inside any function (whether annotated with #[track_caller] or not), calling std::panic::Location::caller() returns a &'static Location<'static> struct with .file(), .line(), and .column() methods:
- In a normal function,
Location::caller()returns the location of theLocation::caller()call itself. - In a
#[track_caller]function,Location::caller()returns the location of the external caller—and propagates transitively through chains of#[track_caller]functions!
use std::panic::Location;
#[track_caller]
fn log_check(condition: bool, label: &str) {
let caller = Location::caller();
println!(
"[{}:{}:{}] check {:?} -> {}",
caller.file(),
caller.line(),
caller.column(),
label,
if condition { "PASS" } else { "FAIL" }
);
}
#[track_caller]
fn assert_even(n: i32) {
// Because both `assert_even` and `log_check` have `#[track_caller]`,
// `Location::caller()` inside `log_check` points all the way back to `main()`!
log_check(n % 2 == 0, "is_even");
}
fn main() {
log_check(2 + 2 == 4, "basic_math");
assert_even(42);
}
[src/main.rs:22:5] check "basic_math" -> PASS
[src/main.rs:23:5] check "is_even" -> PASS
Rules and Restrictions on #[track_caller]
| Rule | Detail |
|---|---|
| Transitive propagation | If function A calls #[track_caller] fn B, which calls #[track_caller] fn C, C receives A's call-site location |
| Trait methods | If #[track_caller] is placed on a trait method declaration, it applies across dyn Trait vtable calls as well as static calls |
| Closures | Closures cannot currently be annotated with #[track_caller] on stable Rust; use a named #[track_caller] fn helper instead |
| FFI incompatibility | #[track_caller] can only be used on "Rust" ABI functions (using it on extern "C" fn fails with E0737) |