1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
extern crate std;

use std::boxed::Box;
use std::panic;

use core::future::Future;
use core::any::Any;
use core::pin::Pin;
use core::task;

#[must_use]
///Wraps to propagate panic as error.
///
///## Example
///
///```rust
///use scope_guard::CatchUnwindFut;
///
///async fn my_fut() -> Result<(), bool> {
///    Err(true)
///}
///
///async fn example() {
///    match CatchUnwindFut(my_fut()).await {
///        Ok(Ok(())) => panic!("Success!?"),
///        Ok(Err(res)) => assert!(res),
///        Err(panic) => std::panic::resume_unwind(panic),
///    }
///}
///```
pub struct CatchUnwindFut<F: panic::UnwindSafe>(pub F);

impl<F: Future + panic::UnwindSafe> Future for CatchUnwindFut<F> {
    type Output = Result<F::Output, Box<dyn Any + Send + 'static>>;

    #[inline(always)]
    fn poll(self: Pin<&mut Self>, ctx: &mut task::Context<'_>) -> task::Poll<Self::Output> {
        let fut = unsafe {
            self.map_unchecked_mut(|this| &mut this.0)
        };

        match panic::catch_unwind(panic::AssertUnwindSafe(|| fut.poll(ctx))) {
            Ok(task::Poll::Pending) => task::Poll::Pending,
            Ok(task::Poll::Ready(res)) => task::Poll::Ready(Ok(res)),
            Err(error) => task::Poll::Ready(Err(error)),
        }
    }
}

///Executes future, making sure to perform cleanup regardless of whether `fut` is successful or
///panics.
///
///## Arguments:
///- `dtor` - Generic callback that accepts `args` as its only incoming parameter;
///- `args` - Generic arguments that are passed as it is to the `dtor`;
///- `fut` - Future to execute before calling `dtor`. Regardless of success, `dtor` is always
///executed.
///
///Returns `Output` of `fut` or panics on error in executing `fut`.
///Regardless of `fut` execution status, `dtor` is always called.
///
///## Example
///
///```rust
///use scope_guard::async_scope;
///
///async fn dtor(_args: ()) {
///    println!("dtor!");
///}
///
///async fn example() {
///    let fut = async {
///        panic!("FAIL")
///    };
///
///    async_scope(dtor, (), fut).await;
///}
///```
pub async fn async_scope<
    R,
    F: Future<Output = R> + panic::UnwindSafe,
    DTORARGS,
    DTOR: Future<Output = ()>,
    DTORFN: FnOnce(DTORARGS) -> DTOR,
>(
    dtor: DTORFN,
    args: DTORARGS,
    fut: F,
) -> R {
    let result = CatchUnwindFut(fut).await;
    let dtor = (dtor)(args);
    dtor.await;
    match result {
        Ok(result) => result,
        Err(error) => std::panic::resume_unwind(error),
    }
}