use std::{
any::Any,
mem,
panic::{catch_unwind, resume_unwind, AssertUnwindSafe, UnwindSafe},
process::abort,
};
pub struct Payload(Option<Box<dyn Any + Send + 'static>>);
impl Payload {
#[inline]
pub fn get(&self) -> &(dyn Any + Send + 'static) {
let Some(payload) = &self.0 else {
unreachable!()
};
payload
}
#[inline]
pub fn get_mut(&mut self) -> &mut (dyn Any + Send + 'static) {
let Some(payload) = &mut self.0 else {
unreachable!()
};
payload
}
#[inline]
pub fn into_inner(mut self) -> Box<dyn Any + Send + 'static> {
self.0.take().unwrap()
}
#[inline]
pub fn drop_or_abort(self) {
drop_or_abort(self.into_inner())
}
#[inline]
pub fn drop_or_forget(self) {
drop_or_forget(self.into_inner())
}
#[inline]
pub fn resume_unwind(self) {
resume_unwind(self.into_inner())
}
}
impl Drop for Payload {
#[inline]
fn drop(&mut self) {
if let Some(payload) = self.0.take() {
drop_or_abort(payload)
}
}
}
#[inline]
#[must_use]
pub fn catch_unwind_or_abort<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Option<R> {
match catch_unwind(f) {
Ok(ok) => Some(ok),
Err(err) => {
drop_or_abort(err);
None
}
}
}
#[inline]
#[must_use]
pub fn catch_unwind_or_forget<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Option<R> {
match catch_unwind(f) {
Ok(ok) => Some(ok),
Err(err) => {
drop_or_forget(err);
None
}
}
}
#[inline]
pub fn catch_unwind_wrapped<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R, Payload> {
catch_unwind(f).map_err(|e| Payload(Some(e)))
}
#[inline]
pub fn drop_or_else<T, F: FnOnce(Box<dyn Any + Send + 'static>) -> E, E>(
value: T,
or_else: F,
) -> Result<(), E> {
catch_unwind(AssertUnwindSafe(move || mem::drop(value))).map_err(or_else)
}
#[inline]
pub fn drop_or_abort<T>(value: T) {
let _ = drop_or_else(value, |_err| abort());
}
#[inline]
pub fn drop_or_forget<T>(value: T) {
let _ = drop_or_else(value, mem::forget);
}
#[cfg(test)]
mod tests {
use super::*;
use std::panic::panic_any;
fn endless_panic() {
struct PanicOnDrop;
impl Drop for PanicOnDrop {
fn drop(&mut self) {
panic_any(Self)
}
}
panic_any(PanicOnDrop)
}
#[test]
fn test_catch_unwind_or_forget() {
assert_eq!(catch_unwind_or_forget(|| "success"), Some("success"));
assert_eq!(catch_unwind_or_forget(endless_panic), None);
}
#[test]
fn test_catch_unwind_wrapped() {
assert!(matches!(catch_unwind_wrapped(|| "success"), Ok("success")));
match catch_unwind(|| match catch_unwind_wrapped(endless_panic) {
Ok(()) => unreachable!(),
Err(payload) => payload.drop_or_forget(),
}) {
Ok(()) => (),
Err(_) => panic!("Payload::drop_or_forget didn't forget"),
}
match catch_unwind(|| match catch_unwind_wrapped(endless_panic) {
Ok(()) => unreachable!(),
Err(payload) => payload.resume_unwind(),
}) {
Ok(()) => panic!("Payload::resume_unwind didn't resume"),
Err(err) => drop_or_forget(err),
}
}
}