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// Re-export proc-macro crate for nested pattern expansion
108#[doc(hidden)]
109pub use handle_this_macros;
110
111// ============================================================
112// Type aliases
113// ============================================================
114
115/// Result type alias.
116///
117/// - `Result<T>` = `core::result::Result<T, Handled>` (type-erased)
118/// - `Result<T, Handled<io::Error>>` = preserves concrete error type
119pub type Result<T, E = Handled> = core::result::Result<T, E>;
120
121/// Result module for `try catch` blocks.
122#[allow(non_snake_case)]
123pub mod result {
124 use super::Handled;
125
126 /// Result type alias for `try catch` blocks.
127 pub type Result<T> = core::result::Result<T, Handled>;
128
129 /// Create an `Ok` result.
130 #[inline]
131 pub fn Ok<T>(v: T) -> Result<T> {
132 core::result::Result::Ok(v)
133 }
134
135 /// Create an `Err` result with automatic conversion to `Handled`.
136 #[inline]
137 pub fn Err<T>(e: impl Into<Handled>) -> Result<T> {
138 core::result::Result::Err(e.into())
139 }
140}
141
142/// Type alias for errors in chain closures.
143#[doc(hidden)]
144#[cfg(feature = "std")]
145pub type __BoxedError = Box<dyn std::error::Error + Send + Sync + 'static>;
146
147// Re-export helper functions for macros
148#[doc(hidden)]
149pub use macros::{
150 __map_try_erased, __with_finally, __wrap_frame,
151 __ThrowExpr, __Thrown,
152 __convert_try_catch_result, __convert_try_catch_result_str,
153 __ErrWrap, __IntoHandled,
154 TryCatchConvert, TryCatchResult,
155};
156
157// ============================================================
158// Loop Signal - Control Flow as Data
159// ============================================================
160
161/// Internal signal type for control flow in loop patterns.
162///
163/// When handlers contain `continue` or `break`, the macro transforms them into
164/// signal values that get translated to actual control flow at the expansion site.
165///
166/// # Why This Exists
167///
168/// Rust prevents control flow from escaping closures. This type allows:
169/// 1. Handlers to "request" control flow via return values
170/// 2. Typed catches to propagate unmatched errors (instead of `unreachable!()`)
171/// 3. Signals to compose through nested try blocks
172///
173/// # Safety
174///
175/// This type is `#[doc(hidden)]` and only used by macro-generated code.
176/// All variants are exhaustively matched - no signals are ever dropped.
177#[doc(hidden)]
178#[derive(Debug, Clone, Copy, PartialEq, Eq)]
179pub enum __LoopSignal<T> {
180 /// Normal completion with a value.
181 Value(T),
182 /// Signal to execute `continue` on the target loop.
183 Continue,
184 /// Signal to execute `break` on the target loop.
185 Break,
186}
187
188impl<T> __LoopSignal<T> {
189 /// Map the inner value, preserving control flow signals.
190 #[inline]
191 pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> __LoopSignal<U> {
192 match self {
193 __LoopSignal::Value(v) => __LoopSignal::Value(f(v)),
194 __LoopSignal::Continue => __LoopSignal::Continue,
195 __LoopSignal::Break => __LoopSignal::Break,
196 }
197 }
198
199 /// Check if this is a control flow signal (not a value).
200 #[inline]
201 pub fn is_control_flow(&self) -> bool {
202 !matches!(self, __LoopSignal::Value(_))
203 }
204
205 /// Extract the value, panicking on control flow signals.
206 ///
207 /// # Panics
208 ///
209 /// Panics if this is `Continue` or `Break`.
210 #[inline]
211 pub fn unwrap_value(self) -> T {
212 match self {
213 __LoopSignal::Value(v) => v,
214 __LoopSignal::Continue => panic!("called unwrap_value on Continue signal"),
215 __LoopSignal::Break => panic!("called unwrap_value on Break signal"),
216 }
217 }
218}
219
220/// Helper to create Ok(LoopSignal::Continue) with inferred type.
221/// Used by transformed control flow in signal mode handlers.
222#[doc(hidden)]
223#[inline]
224pub fn __signal_continue<T>() -> core::result::Result<__LoopSignal<T>, Handled> {
225 core::result::Result::Ok(__LoopSignal::Continue)
226}
227
228/// Non-generic control flow signal.
229/// Used when the value type cannot be inferred (e.g., pure control flow handlers).
230#[doc(hidden)]
231#[derive(Debug, Clone, Copy, PartialEq, Eq)]
232pub enum __ControlSignal {
233 /// Normal completion - value stored separately.
234 Value,
235 /// Signal to execute `continue` on the target loop.
236 Continue,
237 /// Signal to execute `break` on the target loop.
238 Break,
239}
240
241/// Result type for control flow signals where value is stored separately.
242#[doc(hidden)]
243pub type __ControlResult = core::result::Result<__ControlSignal, Handled>;
244
245/// Helper to return Continue signal.
246#[doc(hidden)]
247#[inline]
248pub fn __ctrl_continue() -> __ControlResult {
249 core::result::Result::Ok(__ControlSignal::Continue)
250}
251
252/// Helper to return Break signal.
253#[doc(hidden)]
254#[inline]
255pub fn __ctrl_break() -> __ControlResult {
256 core::result::Result::Ok(__ControlSignal::Break)
257}
258
259/// Helper to store a value and return Value signal.
260/// Uses Option for better type inference than MaybeUninit.
261#[doc(hidden)]
262#[inline]
263pub fn __ctrl_store_value<T>(slot: &mut Option<T>, value: T) -> __ControlResult {
264 *slot = Some(value);
265 core::result::Result::Ok(__ControlSignal::Value)
266}
267
268/// Creates an Option::None with type inferred from a Result reference.
269/// Used to tie the Option's type to the body result's type.
270#[doc(hidden)]
271#[inline]
272pub fn __ctrl_none_like<T, E>(_hint: &core::result::Result<T, E>) -> Option<T> {
273 None
274}
275
276/// Identity function that forces type inference for Result.
277/// Used to make type inference work for nested try blocks.
278#[doc(hidden)]
279#[inline]
280pub fn __force_result_type<T, E>(result: core::result::Result<T, E>) -> core::result::Result<T, E> {
281 result
282}
283
284/// Helper to create Ok(LoopSignal::Break) with inferred type.
285/// Used by transformed control flow in signal mode handlers.
286#[doc(hidden)]
287#[inline]
288pub fn __signal_break<T>() -> core::result::Result<__LoopSignal<T>, Handled> {
289 core::result::Result::Ok(__LoopSignal::Break)
290}
291
292// ============================================================
293// Try block macros
294// ============================================================
295
296/// Internal macro to create a try block that returns Result<T, Box<dyn Error>>.
297/// Body is the success value - use `?` to propagate errors.
298#[doc(hidden)]
299#[macro_export]
300macro_rules! __try_block {
301 ($($body:tt)*) => {
302 (|| -> ::core::result::Result<_, $crate::__BoxedError> {
303 ::core::result::Result::Ok({ $($body)* })
304 })()
305 };
306}
307
308/// Internal macro for async try blocks.
309#[doc(hidden)]
310#[macro_export]
311macro_rules! __async_try_block {
312 ($($body:tt)*) => {
313 (|| async move {
314 let __result: ::core::result::Result<_, $crate::__BoxedError> =
315 ::core::result::Result::Ok({ $($body)* });
316 __result
317 })()
318 };
319}
320
321/// Async finally helper.
322#[doc(hidden)]
323#[inline]
324pub async fn __with_finally_async<T, F, Fut, G>(f: F, finally: G) -> T
325where
326 F: FnOnce() -> Fut,
327 Fut: std::future::Future<Output = T>,
328 G: FnOnce(),
329{
330 let result = f().await;
331 finally();
332 result
333}