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}