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}