panic_message/
lib.rs

1//! A simple utility to take panic payloads, primarily obtained from
2//! obtained from [`std::panic::catch_unwind`] or [`std::panic::set_hook`],
3//! and converting them into messages (`&str`'s)
4//!
5//! # `panic_message`
6//!
7//! [`panic_message`][crate::panic_message] takes a payload from `[std::panic::catch_unwind`] and returns a `&str`,
8//! doing its best attempt to unpack a `&str` message from the payload, defaulting to the
9//! literal `"Box<dyn Any>"` in an attempt to recreate what rustc does.
10//!
11//! ## Examples
12//! ```
13//! use std::panic::catch_unwind;
14//!
15//! let payload = catch_unwind(|| {
16//!     panic!("gus"); }).unwrap_err();
17//!
18//! let msg = panic_message::panic_message(&payload);
19//! assert_eq!("gus", msg);
20//! ```
21//! Non-string payload:
22//! ```
23//! use std::panic::catch_unwind;
24//!
25//! let payload = catch_unwind(|| {
26//!     std::panic::panic_any(1);
27//! }).unwrap_err();
28//!
29//! let msg = panic_message::panic_message(&payload);
30//! assert_eq!("Box<dyn Any>", msg);
31//! ```
32//!
33//! # `get_panic_message`
34//!
35//! [`get_panic_message`][crate::get_panic_message] is similar to `panic_message`, but returns an `Option<&str>`,
36//! returning `None` when it can't unpack a message from the payload
37//!
38//! ## Examples
39//! ```
40//! use std::panic::catch_unwind;
41//!
42//! let payload = catch_unwind(|| {
43//!     panic!("gus");
44//! }).unwrap_err();
45//!
46//! let msg = panic_message::get_panic_message(&payload);
47//! assert_eq!(Some("gus"), msg);
48//! ```
49//! Non-string payload:
50//! ```
51//! use std::panic::catch_unwind;
52//!
53//! let payload = catch_unwind(|| {
54//!     std::panic::panic_any(1);
55//! }).unwrap_err();
56//!
57//! let msg = panic_message::get_panic_message(&payload);
58//! assert_eq!(None, msg);
59//! ```
60//!
61//! # `PanicInfo`
62//!
63//! This library also offers apis for getting messages from [`PanicInfo`][std::panic::PanicInfo`]'s
64//! as returned by [`std::panic::set_hook`]:
65//! - [`panic_info_message`][crate::panic_info_message] is similar
66//! to [`panic_message`][crate::panic_message] and has a default string `"Box<dyn Any>"`
67//! - [`get_panic_info_message`][crate::get_panic_info_message] is similar
68//! to [`get_panic_message`][crate::get_panic_message] and returns an `Option<&str>`
69//!
70//! ## Example
71//!
72//! ```
73//! std::panic::set_hook(Box::new(|pi| {
74//!     println!("{}", panic_message::panic_info_message(pi));
75//!     println!("{:?}", panic_message::get_panic_info_message(pi));
76//! }));
77//! ```
78//!
79//! # Note
80//!
81//! This library has methods that take values that are returned by standard mechanisms to obtain
82//! panic payloads, as opposed to a single generic method that takes `&dyn Any`.
83//! This is to prevent misuse.
84//! For example, the reason to take `PanicInfo` and not the `&dyn Any` as returned by
85//! [`PanicInfo::payload`][std::panic::PanicInfo::payload] is because `Box<dyn Any>`
86//! can be coerced into `&dyn Any`, which would make a method that takes `&dyn Any` possible
87//! to misuse with a payload from [`std::panic::catch_unwind`].
88//!
89use std::any::Any;
90use std::panic::PanicInfo;
91
92/// Attempt to produce a `&str` message (with a default)
93/// from a [`std::panic::catch_unwind`] payload.
94/// See [module docs][crate] for usage.
95pub fn panic_message(payload: &Box<dyn Any + Send>) -> &str {
96    imp::get_panic_message(payload.as_ref()).unwrap_or({
97        // Copy what rustc does in the default panic handler
98        "Box<dyn Any>"
99    })
100}
101
102/// Attempt to produce a `&str` message
103/// from a [`std::panic::catch_unwind`] payload.
104/// See [module docs][crate] for usage.
105pub fn get_panic_message(payload: &Box<dyn Any + Send>) -> Option<&str> {
106    imp::get_panic_message(payload.as_ref())
107}
108
109/// Attempt to produce a `&str` message (with a default)
110/// from a [`std::panic::PanicInfo`].
111/// See [module docs][crate] for usage.
112pub fn panic_info_message<'pi>(panic_info: &'pi PanicInfo<'_>) -> &'pi str {
113    imp::get_panic_message(panic_info.payload()).unwrap_or({
114        // Copy what rustc does in the default panic handler
115        "Box<dyn Any>"
116    })
117}
118
119/// Attempt to produce a `&str` message (with a default)
120/// from a [`std::panic::PanicInfo`].
121/// See [module docs][crate] for usage.
122pub fn get_panic_info_message<'pi>(panic_info: &'pi PanicInfo<'_>) -> Option<&'pi str> {
123    imp::get_panic_message(panic_info.payload())
124}
125
126mod imp {
127    use super::*;
128    /// Attempt to produce a message from a borrowed `dyn Any`. Note that care must be taken
129    /// when calling this to avoid a `Box<dyn Any>` being coerced to a `dyn Any` itself.
130    pub(super) fn get_panic_message(payload: &(dyn Any + Send)) -> Option<&str> {
131        // taken from: https://github.com/rust-lang/rust/blob/4b9f4b221b92193c7e95b1beb502c6eb32c3b613/library/std/src/panicking.rs#L194-L200
132        match payload.downcast_ref::<&'static str>() {
133            Some(msg) => Some(*msg),
134            None => match payload.downcast_ref::<String>() {
135                Some(msg) => Some(msg.as_str()),
136                // Copy what rustc does in the default panic handler
137                None => None,
138            },
139        }
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146    use std::panic::catch_unwind;
147
148    #[test]
149    fn basic() {
150        let payload = catch_unwind(|| panic!("gus")).unwrap_err();
151
152        let msg = panic_message(&payload);
153
154        assert_eq!("gus", msg);
155    }
156
157    #[test]
158    fn string() {
159        let payload = catch_unwind(|| std::panic::panic_any("gus".to_string())).unwrap_err();
160
161        let msg = panic_message(&payload);
162
163        assert_eq!("gus", msg);
164    }
165
166    #[test]
167    fn expect() {
168        let payload = catch_unwind(|| {
169            // Note this is a reference to a local string
170            // but expect internally turns it back into a String for the payload
171            None::<()>.expect(&format!("{}", "gus"))
172        })
173        .unwrap_err();
174
175        let msg = panic_message(&payload);
176
177        assert_eq!("gus", msg);
178    }
179
180    #[test]
181    fn something_else() {
182        let payload = catch_unwind(|| {
183            std::panic::panic_any(1);
184        })
185        .unwrap_err();
186
187        let msg = panic_message(&payload);
188
189        assert_eq!("Box<dyn Any>", msg);
190    }
191}