Pattern for extracting `PanicInfo` during `catch_unwind`

Hey everyone,

Is there a pattern to get the full PanicInfo with location information within catch_unwind? Or a pattern to copy this information somewhere within a set_hook. The result from panic::catch_unwind(...) can be downcasted to a &str/String (thanks Discord for that hint) but I'm hoping to get the file and line information from PanicInfo.

Context: some C functions provide an error message buffer for a callee function, which will get displayed to the user. The error result from catch_unwind will provide a message like:

"called `Option::unwrap()` on a `None` value"

But that's not nearly as useful without file name and line numbers. So, I think I'm looking for something in the set_hook closure that lets me copy them somewhere:

// I'm a thin interface between C and rust
extern "C" fn rust_wrapper(errmsg: &mut String) {
    let prev_hook = panic::take_hook();

    panic::set_hook(Box::new(|ph| {
        // Can I do something here to save the entire PanicInfo?
        println!("{ph:#?}");
    }));

    let result = panic::catch_unwind(|| {
        rust_implementation();
    }).unwrap_err();
    
    panic::set_hook(prev_hook);

    // Don't worry, there will be no unwrap in the real world
    errmsg.push_str(*result.downcast::<&str>().unwrap());
}

But that has proven difficult. Does anybody have any suggestions?

(Of course something like .expect() or a displayable error would be the best case here. But it's nice for users to be able to report some sort of useful error if there's something like a bug in a dependency.)

Full playground link: Rust Playground

Panics are not meant for regular recoverable errors, but instead for cases where there is a bug in your code. catch_unwind allows you to catch panics (unless panic=abort was specified when compiling by the end user), but this is not meant for reporting it as a regular error to the caller, but to allow for example for abandoning processing of a request and instead returning a 500 internal server error from a web server or to propagate ot through C code back into rust code and then resume unwinding using resume_unwind. This is also why panics always print a message to stderr, even when you use catch_unwind.

For errors meant to be handled by the caller you should use Result instead. The error there can have as much detail as you want. Be it an error location or a full backtrace.

1 Like

Yes, absolutely - I am aware that Result is the correct solution, and that unwinding should never ever be used for error handling. My library strongly encourages this, and any Err message is copied to the error buffer.

However - it's impossible to guarantee that there are zero panics deeply nested somewhere, or as part of a dependency, from something like an unchecked arr[i] or None.unwrap(). (programming errors that should not ever happen but might possibly). I currently write the downcasted message to the error buffer, but just wish I could also provide location information so users have something useful to report in these extremely rare, potentially nonreproducable cases.

(the downcasted error messages for the above are currently called Option::unwrap() on a None value or errors: index out of bounds: the len is 2 but the index is 2. They're still generally useful, but not very narrowing when unwrap may be called a few thousand times in code).

Have you tried using a thread local? That seems like really the only way to pass arbitrary data out of a panic hook

1 Like

I am also trying to get the PanicInfo from a panic.

As you can see, oddly, downcasting a panic to a PanicInfo is not possible (why?) and downcasting to a &str returns the payload but not the location.

use std::panic;

fn main() {
    let p = panic::catch_unwind(|| panic!("A wild panic appeared!"));
    let err = p.unwrap_err();
    println!("{:#?}", err.downcast_ref::<panic::PanicInfo>()); // None
    println!("{:#?}", err.downcast_ref::<&str>()); // Some("A wild panic appeared!")
}

There Is a away to smuggle a backtrace into the panic, but if my program is multithreaded, i'm afraid this approach might become a little clusmy to program?

Does anyone know otherwise how to extract the PanicInfo from a panic?

The object you get when you catch a panic is the value that was passed to panic!. Only the panic hook receives the PanicInfo.

2 Likes