//! The exception context that can be use as a `static` variable based on thread-local. extern crate std; use std::thread::LocalKey; /// `ExceptionContext` provides the context for throwing and catching exception. /// /// This context wraps over a thread-local [crate::unsync::ExceptionContext], /// and you can store it as a static variable, so that you don't need to pass the /// context through argument. /// /// Example: /// ``` /// use asynx::global::ExceptionContext; /// /// thread_local! {static EXCEPTION_LOCAL: asynx::unsync::ExceptionContext = Default::default(); } /// pub static CTX: ExceptionContext = ExceptionContext::new(&EXCEPTION_LOCAL); /// /// async fn perform(success: bool) -> String { /// if success { /// "success".to_string() /// } else { /// CTX.throw("failed".to_string()).await /// } /// } /// /// tokio_test::block_on(async { /// let r = CTX /// .catch(async { /// assert_eq!("success".to_string(), perform(true).await); /// perform(false).await; /// unreachable!() // The previous statement throws an exception. /// }) /// .await; /// assert_eq!(Err("failed".to_string()), r) /// }); /// ``` pub struct ExceptionContext { inner: &'static LocalKey>, } impl ExceptionContext { /// Create a new exception context from a thread-local unsync context. pub const fn new( local: &'static LocalKey>, ) -> ExceptionContext { Self { inner: local } } /// Throws an exception. You should always `await` the result. /// /// Example: /// /// ```rust /// use asynx::global::ExceptionContext; /// /// thread_local! {static EXCEPTION_LOCAL: asynx::unsync::ExceptionContext = Default::default(); } /// static CTX: ExceptionContext = ExceptionContext::new(&EXCEPTION_LOCAL); /// /// tokio_test::block_on(async { /// let r = CTX.catch(async { /// CTX.throw("failed".to_string()).await; /// unreachable!() /// }).await; /// assert_eq!(Err("failed".to_string()), r) /// }) /// ``` pub async fn throw(&self, e: E) -> ! { self.inner.with(|ctx| { crate::unsync::set_exception(ctx, e); }); core::future::pending().await } /// Creates the wrapper future that catches the exception. /// /// Example: /// /// ``` /// use asynx::global::ExceptionContext; /// /// thread_local! {static EXCEPTION_LOCAL: asynx::unsync::ExceptionContext = Default::default(); } /// static CTX: ExceptionContext = ExceptionContext::new(&EXCEPTION_LOCAL); /// /// tokio_test::block_on(async { /// let r = CTX.catch(async { /// "success".to_string() /// }).await; /// assert_eq!(Ok("success".to_string()), r); /// /// let r = CTX.catch(async { /// CTX.throw("failed".to_string()).await; /// unreachable!() /// }).await; /// assert_eq!(Err("failed".to_string()), r) /// }) /// ``` /// /// Like the unsync version, the `catch` can be called multiple times. /// /// ``` /// use asynx::global::ExceptionContext; /// /// thread_local! {static EXCEPTION_LOCAL: asynx::unsync::ExceptionContext = Default::default(); } /// static CTX: ExceptionContext = ExceptionContext::new(&EXCEPTION_LOCAL); /// /// tokio_test::block_on(async { /// let r = CTX.catch(async { /// let r = CTX.catch(async { /// CTX.throw(1).await /// }).await; /// assert_eq!(Err(1), r); /// /// let r = CTX.catch(async { /// CTX.throw(2).await /// }).await; /// assert_eq!(Err(2), r); /// /// CTX.throw(3).await; /// }).await; /// assert_eq!(Err(3), r) /// }) /// ``` pub fn catch(&self, f: Fu) -> Catching { Catching { ctx: self.inner, future: f, } } } pin_project_lite::pin_project! { /// A wrapper future that catches the exception for thread-local context. /// /// It outputs a result with the exception as error. pub struct Catching { ctx: &'static LocalKey>, #[pin] future: F, } } impl core::future::Future for Catching { type Output = Result; fn poll( self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>, ) -> core::task::Poll { let this = self.project(); this.ctx .with(|ctx| crate::unsync::poll_catching(ctx, this.future, cx)) } }