panic_ext/
lib.rs

1//!Extension library to panic facilities to make it more usable
2//!
3//!Requires Rust 1.81
4//!
5//!## Features
6//!
7//!- `alloc` - Enables usage of `alloc` types
8//!- `std` - Enables `std::error::Error` impl on panic details. Implies `alloc`
9
10#![no_std]
11#![warn(missing_docs)]
12#![allow(clippy::style)]
13
14#[cfg(feature = "alloc")]
15extern crate alloc;
16#[cfg(feature = "std")]
17extern crate std;
18
19use core::fmt;
20use core::panic::{Location, PanicInfo};
21
22///Panic's message definition
23pub trait Message: fmt::Display + fmt::Debug {}
24
25impl<T: fmt::Display + fmt::Debug> Message for T {}
26
27#[track_caller]
28#[inline(always)]
29///Retrieves panic details
30pub fn panic_details<'a>(payload: &'a impl PanicInfoExt<'a>) -> PanicDetails<'a, impl Message + 'a> {
31    payload.panic_details()
32}
33
34#[inline(always)]
35///Retrieves panic message
36pub fn panic_message<'a>(payload: &'a impl PanicInfoExt<'a>) -> impl Message + 'a {
37    payload.panic_message()
38}
39
40#[inline(always)]
41///Retrieves panic message from the dynamic payload
42///
43///Note that while it accepts any type, it is designed to work with panic's payload
44pub fn downcast_payload<'a>(payload: &'a (dyn core::any::Any + Send + 'static)) -> &'a dyn Message {
45    const DEFAULT_MESSAGE: &'static str = "panic occurred";
46    match payload.downcast_ref::<&'static str>() {
47        Some(message) => message,
48        #[cfg(feature = "alloc")]
49        None => match payload.downcast_ref::<alloc::string::String>() {
50            Some(message) => message,
51            None => &DEFAULT_MESSAGE,
52        },
53        #[cfg(not(feature = "alloc"))]
54        None => &DEFAULT_MESSAGE,
55    }
56}
57
58#[derive(Clone, Copy, Debug)]
59///Panic details
60pub struct PanicDetails<'a, M: 'a> {
61    ///Panic location
62    ///
63    ///Location is optional in PanicInfo.
64    ///
65    ///Therefore if for some reason standard library shall remove it,
66    ///instead it will be initialized with location of where message function is called
67    pub location: &'a Location<'a>,
68    ///Panic message which can string or
69    ///[core::fmt::Arguments](https://doc.rust-lang.org/core/fmt/struct.Arguments.html)
70    pub message: M,
71}
72
73impl<M: Message> core::error::Error for PanicDetails<'_, M> {
74}
75
76impl<M: Message> fmt::Display for PanicDetails<'_, M> {
77    #[inline(always)]
78    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
79        fmt::Display::fmt(&self.location, fmt)?;
80        fmt.write_str(": ")?;
81        fmt::Display::fmt(&self.message, fmt)
82    }
83}
84
85///Extension trait to provide better API for PanicInfo
86pub trait PanicInfoExt<'a> {
87    ///Retrieves underlying panic message
88    fn panic_message(&'a self) -> impl Message + 'a;
89
90    #[track_caller]
91    #[inline(always)]
92    ///Access uniform details of panic
93    ///
94    ///Default implementation uses location of this function call.
95    ///When panic location is known, it is overridden with specialized version
96    fn panic_details(&'a self) -> PanicDetails<'a, impl Message + 'a> {
97        PanicDetails {
98            location: Location::caller(),
99            message: self.panic_message(),
100        }
101    }
102}
103
104impl<'a> PanicInfoExt<'a> for PanicInfo<'a> {
105    #[inline(always)]
106    fn panic_message(&'a self) -> impl Message + 'a {
107        self.message()
108    }
109
110    #[track_caller]
111    #[inline(always)]
112    fn panic_details(&'a self) -> PanicDetails<'a, impl Message + 'a> {
113        let location = match self.location() {
114            Some(location) => location,
115            None => Location::caller(),
116        };
117        PanicDetails {
118            location,
119            message: self.panic_message()
120        }
121    }
122}
123
124impl<'a> PanicInfoExt<'a> for &'a (dyn core::any::Any + Send + 'static) {
125    #[inline(always)]
126    fn panic_message(&'a self) -> impl Message + 'a {
127        downcast_payload(*self)
128    }
129}
130
131#[cfg(feature = "alloc")]
132impl<'a> PanicInfoExt<'a> for alloc::boxed::Box<dyn core::any::Any + Send + 'static> {
133    #[inline(always)]
134    fn panic_message(&'a self) -> impl Message + 'a {
135        downcast_payload(self)
136    }
137}
138
139#[cfg(feature = "std")]
140impl<'a> PanicInfoExt<'a> for std::panic::PanicHookInfo<'a> {
141    #[inline(always)]
142    fn panic_message(&'a self) -> impl Message + 'a {
143        downcast_payload(self.payload())
144    }
145
146    #[track_caller]
147    #[inline(always)]
148    fn panic_details(&'a self) -> PanicDetails<'a, impl Message + 'a> {
149        let location = match self.location() {
150            Some(location) => location,
151            None => Location::caller(),
152        };
153        PanicDetails {
154            location,
155            message: self.panic_message()
156        }
157    }
158}