#292 Jul 31, 2026

292. panic::set_hook — You Caught the Panic, but It Still Screamed to stderr

catch_unwind gave you a tidy Err — yet the full “thread panicked at …” message hit stderr anyway. std::panic::set_hook owns that output.

Yesterday’s bite kept the worker loop alive with catch_unwind. But run it and stderr is still full of noise, because the default panic hook prints before unwinding even starts:

1
2
3
4
5
6
7
use std::panic;

let _ = panic::catch_unwind(|| run_job(2));
// You handled the Err — but stderr still shows:
// thread 'main' panicked at src/main.rs:4:9:
// job 2 choked
// note: run with `RUST_BACKTRACE=1` ...

The hook and the unwind are separate mechanisms. catch_unwind decides where unwinding stops; the hook decides what gets printed. Replace it and the message is yours — one line, your format, your logger:

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

panic::set_hook(Box::new(|info| {
    let loc = info.location().unwrap();
    let msg = info.payload_as_str().unwrap_or("?");
    eprintln!("[worker] {msg} ({}:{})",
        loc.file(), loc.line());
}));

let _ = panic::catch_unwind(|| run_job(2));
// stderr: [worker] job 2 choked (src/main.rs:4)

info is a PanicHookInfo: location() gives file, line, and column, and payload_as_str() (stable since 1.91) recovers the message when it’s a String or &str — no downcast dance.

Three things to know. The hook is global and process-wide — install it once at startup, not per-job, and note that set_hook swaps the hook for every thread. It runs even under panic = "abort", which makes it the one place to flush logs before the process dies. And take_hook() returns the previous hook as a value, so you can restore the default — or wrap it: grab the old hook, install a closure that logs your line and then calls the old one.

Silencing expected panics in tests is the classic use: swap in a no-op hook, catch_unwind the call you expect to panic, restore with take_hook — assertion output stays clean.

← Previous 291. catch_unwind — One Panicking Job Shouldn't Kill the Whole Worker Next → 293. thread::panicking — A Second Panic in Drop Doesn't Unwind, It Aborts