catch_unwind/
lib.rs

1//! This crate provides wrappers for [`std::panic::catch_unwind`] that handle the
2//! edge case of the caught panic payload itself panicing when dropped.
3
4use std::{
5    any::Any,
6    mem,
7    panic::{catch_unwind, resume_unwind, AssertUnwindSafe, UnwindSafe},
8    process::abort,
9};
10
11/// Unwinding payload wrapped to abort by default if it panics on drop
12pub struct Payload(Option<Box<dyn Any + Send + 'static>>);
13
14impl Payload {
15    /// Get a reference to the payload
16    #[inline]
17    pub fn get(&self) -> &(dyn Any + Send + 'static) {
18        let Some(payload) = &self.0 else {
19            unreachable!()
20        };
21        payload
22    }
23
24    /// Get a mutable reference to the payload
25    #[inline]
26    pub fn get_mut(&mut self) -> &mut (dyn Any + Send + 'static) {
27        let Some(payload) = &mut self.0 else {
28            unreachable!()
29        };
30        payload
31    }
32
33    /// Get the payload itself. This may panic when dropped
34    #[inline]
35    pub fn into_inner(mut self) -> Box<dyn Any + Send + 'static> {
36        self.0.take().unwrap()
37    }
38
39    /// Drop the payload and abort the process if doing so panics
40    #[inline]
41    pub fn drop_or_abort(self) {
42        drop_or_abort(self.into_inner())
43    }
44
45    /// Drop the payload. If doing so panics, `mem::forget` the new payload
46    #[inline]
47    pub fn drop_or_forget(self) {
48        drop_or_forget(self.into_inner())
49    }
50
51    /// Resume unwinding with this payload
52    #[inline]
53    pub fn resume_unwind(self) {
54        resume_unwind(self.into_inner())
55    }
56}
57
58impl Drop for Payload {
59    #[inline]
60    fn drop(&mut self) {
61        if let Some(payload) = self.0.take() {
62            drop_or_abort(payload)
63        }
64    }
65}
66
67/// Invoke the provided closure and catch any unwinding panics that may occur. If the panic
68/// payload panics when dropped, abort the process.
69///
70/// Returns `Some` if no panics were caught and `None` otherwise.
71///
72/// See [`std::panic::catch_unwind`] for more information.
73#[inline]
74#[must_use]
75pub fn catch_unwind_or_abort<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Option<R> {
76    match catch_unwind(f) {
77        Ok(ok) => Some(ok),
78        Err(err) => {
79            drop_or_abort(err);
80            None
81        }
82    }
83}
84
85/// Invoke the provided closure and catch any unwinding panics that may occur. If the panic
86/// payload panics when dropped, `mem::forget` the new panic payload and return `None`.
87///
88/// Returns `Some` if no panics were caught and `None` otherwise.
89///
90/// See [`std::panic::catch_unwind`] for more information.
91#[inline]
92#[must_use]
93pub fn catch_unwind_or_forget<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Option<R> {
94    match catch_unwind(f) {
95        Ok(ok) => Some(ok),
96        Err(err) => {
97            drop_or_forget(err);
98            None
99        }
100    }
101}
102
103/// Invoke the provided closure and catch any unwinding panics that may occur. This wraps
104/// the unwinding payload in [`Payload`], which will abort if it panics on drop by default.
105/// You can use the methods of `Payload` to change this behaviour.
106///
107/// Returns `Ok` if no panics were caught and `Err(Payload)` otherwise.
108///
109/// See [`std::panic::catch_unwind`] for more information.
110#[inline]
111pub fn catch_unwind_wrapped<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R, Payload> {
112    catch_unwind(f).map_err(|e| Payload(Some(e)))
113}
114
115/// Drop a value. If dropping the value results in an unwinding panic, call the provided closure
116/// with the panic payload.
117#[inline]
118pub fn drop_or_else<T, F: FnOnce(Box<dyn Any + Send + 'static>) -> E, E>(
119    value: T,
120    or_else: F,
121) -> Result<(), E> {
122    catch_unwind(AssertUnwindSafe(move || mem::drop(value))).map_err(or_else)
123}
124
125/// Drop a value. If dropping the value results in an unwinding panic, abort the process.
126#[inline]
127pub fn drop_or_abort<T>(value: T) {
128    let _ = drop_or_else(value, |_err| abort());
129}
130
131/// Drop a value. If dropping the value results in an unwinding panic, `mem::forget` the panic payload.
132#[inline]
133pub fn drop_or_forget<T>(value: T) {
134    let _ = drop_or_else(value, mem::forget);
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140    use std::panic::panic_any;
141
142    fn endless_panic() {
143        struct PanicOnDrop;
144
145        impl Drop for PanicOnDrop {
146            fn drop(&mut self) {
147                panic_any(Self)
148            }
149        }
150
151        panic_any(PanicOnDrop)
152    }
153
154    #[test]
155    fn test_catch_unwind_or_forget() {
156        assert_eq!(catch_unwind_or_forget(|| "success"), Some("success"));
157        assert_eq!(catch_unwind_or_forget(endless_panic), None);
158    }
159
160    #[test]
161    fn test_catch_unwind_wrapped() {
162        assert!(matches!(catch_unwind_wrapped(|| "success"), Ok("success")));
163
164        match catch_unwind(|| match catch_unwind_wrapped(endless_panic) {
165            Ok(()) => unreachable!(),
166            Err(payload) => payload.drop_or_forget(),
167        }) {
168            Ok(()) => (),
169            Err(_) => panic!("Payload::drop_or_forget didn't forget"),
170        }
171
172        match catch_unwind(|| match catch_unwind_wrapped(endless_panic) {
173            Ok(()) => unreachable!(),
174            Err(payload) => payload.resume_unwind(),
175        }) {
176            Ok(()) => panic!("Payload::resume_unwind didn't resume"),
177            Err(err) => drop_or_forget(err),
178        }
179    }
180}