Skip to main content

handle_this/
lib.rs

1//! handle-this - Ergonomic error handling with try/catch/throw/inspect/finally
2//!
3//! # Overview
4//!
5//! `handle-this` provides composable error handling with automatic stack traces.
6//! All invocations return `Result<T>` - no hidden control flow.
7//!
8//! # Quick Start
9//!
10//! ```
11//! use handle_this::{handle, Result};
12//!
13//! fn load_data(path: &str) -> Result<String> {
14//!     handle!{ try { std::fs::read_to_string(path)? } with "reading" }
15//! }
16//! ```
17//!
18//! # Patterns
19//!
20//! | Pattern | Description |
21//! |---------|-------------|
22//! | `try { }` | Execute, wrap error with trace |
23//! | `try { } catch e { }` | Recover from error |
24//! | `try { } catch Type(e) { }` | Recover only specific type |
25//! | `try { } throw e { }` | Transform error |
26//! | `try { } inspect e { }` | Side effect, then propagate |
27//! | `try { } finally { }` | Cleanup always runs |
28//! | `try { } with "ctx"` | Add context |
29//! | `async try { }` | Async version |
30//! | `try for x in iter { }` | First success |
31//! | `try any x in iter { }` | Alias for try for |
32//! | `try all x in iter { }` | Collect all results |
33//! | `try while cond { }` | Retry loop |
34
35#![cfg_attr(not(feature = "std"), no_std)]
36
37#[cfg(not(feature = "std"))]
38extern crate alloc;
39
40// ============================================================
41// Modules
42// ============================================================
43
44mod handled;
45mod ext;
46mod macros;
47
48// ============================================================
49// Re-exports
50// ============================================================
51
52pub use handled::{Handled, FrameView, Error, StringError, TryCatch, Value, IntoValue};
53pub use ext::HandleExt;
54
55// Internal helper for macros
56#[doc(hidden)]
57#[cfg(feature = "std")]
58pub use handled::__wrap_any;
59
60// ============================================================
61// Type aliases
62// ============================================================
63
64/// Result type alias.
65///
66/// - `Result<T>` = `core::result::Result<T, Handled>` (type-erased)
67/// - `Result<T, Handled<io::Error>>` = preserves concrete error type
68pub type Result<T, E = Handled> = core::result::Result<T, E>;
69
70/// Result module for `try catch` blocks.
71#[allow(non_snake_case)]
72pub mod result {
73    use super::Handled;
74
75    /// Result type alias for `try catch` blocks.
76    pub type Result<T> = core::result::Result<T, Handled>;
77
78    /// Create an `Ok` result.
79    #[inline]
80    pub fn Ok<T>(v: T) -> Result<T> {
81        core::result::Result::Ok(v)
82    }
83
84    /// Create an `Err` result with automatic conversion to `Handled`.
85    #[inline]
86    pub fn Err<T>(e: impl Into<Handled>) -> Result<T> {
87        core::result::Result::Err(e.into())
88    }
89}
90
91/// Type alias for errors in chain closures.
92#[doc(hidden)]
93#[cfg(feature = "std")]
94pub type __BoxedError = Box<dyn std::error::Error + Send + Sync + 'static>;
95
96// Re-export helper functions for macros
97#[doc(hidden)]
98pub use macros::{
99    __map_try_erased, __with_finally, __wrap_frame,
100    __ThrowExpr, __Thrown,
101    __convert_try_catch_result, __convert_try_catch_result_str,
102    __ErrWrap, __IntoHandled,
103    TryCatchConvert, TryCatchResult,
104};
105
106// ============================================================
107// Loop Signal - Control Flow as Data
108// ============================================================
109
110/// Internal signal type for control flow in loop patterns.
111///
112/// When handlers contain `continue` or `break`, the macro transforms them into
113/// signal values that get translated to actual control flow at the expansion site.
114///
115/// # Why This Exists
116///
117/// Rust prevents control flow from escaping closures. This type allows:
118/// 1. Handlers to "request" control flow via return values
119/// 2. Typed catches to propagate unmatched errors (instead of `unreachable!()`)
120/// 3. Signals to compose through nested try blocks
121///
122/// # Safety
123///
124/// This type is `#[doc(hidden)]` and only used by macro-generated code.
125/// All variants are exhaustively matched - no signals are ever dropped.
126#[doc(hidden)]
127#[derive(Debug, Clone, Copy, PartialEq, Eq)]
128pub enum __LoopSignal<T> {
129    /// Normal completion with a value.
130    Value(T),
131    /// Signal to execute `continue` on the target loop.
132    Continue,
133    /// Signal to execute `break` on the target loop.
134    Break,
135}
136
137impl<T> __LoopSignal<T> {
138    /// Map the inner value, preserving control flow signals.
139    #[inline]
140    pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> __LoopSignal<U> {
141        match self {
142            __LoopSignal::Value(v) => __LoopSignal::Value(f(v)),
143            __LoopSignal::Continue => __LoopSignal::Continue,
144            __LoopSignal::Break => __LoopSignal::Break,
145        }
146    }
147
148    /// Check if this is a control flow signal (not a value).
149    #[inline]
150    pub fn is_control_flow(&self) -> bool {
151        !matches!(self, __LoopSignal::Value(_))
152    }
153
154    /// Extract the value, panicking on control flow signals.
155    ///
156    /// # Panics
157    ///
158    /// Panics if this is `Continue` or `Break`.
159    #[inline]
160    pub fn unwrap_value(self) -> T {
161        match self {
162            __LoopSignal::Value(v) => v,
163            __LoopSignal::Continue => panic!("called unwrap_value on Continue signal"),
164            __LoopSignal::Break => panic!("called unwrap_value on Break signal"),
165        }
166    }
167}
168
169/// Helper to create Ok(LoopSignal::Continue) with inferred type.
170/// Used by transformed control flow in signal mode handlers.
171#[doc(hidden)]
172#[inline]
173pub fn __signal_continue<T>() -> core::result::Result<__LoopSignal<T>, Handled> {
174    core::result::Result::Ok(__LoopSignal::Continue)
175}
176
177/// Non-generic control flow signal.
178/// Used when the value type cannot be inferred (e.g., pure control flow handlers).
179#[doc(hidden)]
180#[derive(Debug, Clone, Copy, PartialEq, Eq)]
181pub enum __ControlSignal {
182    /// Normal completion - value stored separately.
183    Value,
184    /// Signal to execute `continue` on the target loop.
185    Continue,
186    /// Signal to execute `break` on the target loop.
187    Break,
188}
189
190/// Result type for control flow signals where value is stored separately.
191#[doc(hidden)]
192pub type __ControlResult = core::result::Result<__ControlSignal, Handled>;
193
194/// Helper to return Continue signal.
195#[doc(hidden)]
196#[inline]
197pub fn __ctrl_continue() -> __ControlResult {
198    core::result::Result::Ok(__ControlSignal::Continue)
199}
200
201/// Helper to return Break signal.
202#[doc(hidden)]
203#[inline]
204pub fn __ctrl_break() -> __ControlResult {
205    core::result::Result::Ok(__ControlSignal::Break)
206}
207
208/// Helper to store a value and return Value signal.
209/// Uses Option for better type inference than MaybeUninit.
210#[doc(hidden)]
211#[inline]
212pub fn __ctrl_store_value<T>(slot: &mut Option<T>, value: T) -> __ControlResult {
213    *slot = Some(value);
214    core::result::Result::Ok(__ControlSignal::Value)
215}
216
217/// Creates an Option::None with type inferred from a Result reference.
218/// Used to tie the Option's type to the body result's type.
219#[doc(hidden)]
220#[inline]
221pub fn __ctrl_none_like<T, E>(_hint: &core::result::Result<T, E>) -> Option<T> {
222    None
223}
224
225/// Identity function that forces type inference for Result.
226/// Used to make type inference work for nested try blocks.
227#[doc(hidden)]
228#[inline]
229pub fn __force_result_type<T, E>(result: core::result::Result<T, E>) -> core::result::Result<T, E> {
230    result
231}
232
233/// Helper to create Ok(LoopSignal::Break) with inferred type.
234/// Used by transformed control flow in signal mode handlers.
235#[doc(hidden)]
236#[inline]
237pub fn __signal_break<T>() -> core::result::Result<__LoopSignal<T>, Handled> {
238    core::result::Result::Ok(__LoopSignal::Break)
239}
240
241// ============================================================
242// Try block macros
243// ============================================================
244
245/// Internal macro to create a try block that returns Result<T, Box<dyn Error>>.
246/// Body is the success value - use `?` to propagate errors.
247#[doc(hidden)]
248#[macro_export]
249macro_rules! __try_block {
250    ($($body:tt)*) => {
251        (|| -> ::core::result::Result<_, $crate::__BoxedError> {
252            ::core::result::Result::Ok({ $($body)* })
253        })()
254    };
255}
256
257/// Internal macro for async try blocks.
258#[doc(hidden)]
259#[macro_export]
260macro_rules! __async_try_block {
261    ($($body:tt)*) => {
262        (|| async move {
263            let __result: ::core::result::Result<_, $crate::__BoxedError> =
264                ::core::result::Result::Ok({ $($body)* });
265            __result
266        })()
267    };
268}
269
270/// Async finally helper.
271#[doc(hidden)]
272#[inline]
273pub async fn __with_finally_async<T, F, Fut, G>(f: F, finally: G) -> T
274where
275    F: FnOnce() -> Fut,
276    Fut: std::future::Future<Output = T>,
277    G: FnOnce(),
278{
279    let result = f().await;
280    finally();
281    result
282}