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