stdx/
panic_context.rs

1//! A micro-crate to enhance panic messages with context info.
2
3use std::{cell::RefCell, panic, sync::Once};
4
5/// Dummy for leveraging RAII cleanup to pop frames.
6#[must_use]
7pub struct PanicContext {
8    // prevent arbitrary construction
9    _priv: (),
10}
11
12impl Drop for PanicContext {
13    fn drop(&mut self) {
14        with_ctx(|ctx| assert!(ctx.pop().is_some()));
15    }
16}
17
18pub fn enter(frame: String) -> PanicContext {
19    #[expect(clippy::print_stderr, reason = "already panicking anyway")]
20    fn set_hook() {
21        let default_hook = panic::take_hook();
22        panic::set_hook(Box::new(move |panic_info| {
23            with_ctx(|ctx| {
24                if !ctx.is_empty() {
25                    eprintln!("Panic context:");
26                    for frame in ctx.iter() {
27                        eprintln!("> {frame}\n");
28                    }
29                }
30            });
31            default_hook(panic_info);
32        }));
33    }
34
35    static SET_HOOK: Once = Once::new();
36    SET_HOOK.call_once(set_hook);
37
38    with_ctx(|ctx| ctx.push(frame));
39    PanicContext { _priv: () }
40}
41
42fn with_ctx(f: impl FnOnce(&mut Vec<String>)) {
43    thread_local! {
44        static CTX: RefCell<Vec<String>> = const { RefCell::new(Vec::new()) };
45    }
46    CTX.with(|ctx| f(&mut ctx.borrow_mut()));
47}