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}