rust_try_catch/lib.rs
1#![deny(missing_docs)]
2
3#![doc = include_str!("../README.md")]
4
5use std::any::Any;
6use std::backtrace::Backtrace;
7use std::panic::AssertUnwindSafe;
8
9#[cfg(not(panic = "unwind"))]
10compile_error!("Try catch only works when panic = \"unwind\"");
11
12/// A flexible `try_catch` macro that provides structured error and panic handling
13/// with an optional `finally` block for cleanup.
14///
15/// # Description
16/// The `try_catch!` macro allows you to define a `try` block for executing code, followed by multiple `catch`
17/// blocks for handling exceptions or panics. Additionally, a `finally` block can be specified for cleanup actions.
18///
19/// # Syntax
20/// ```text
21/// try_catch! {
22/// try {
23/// <try_block>
24/// } catch (<exception_pattern> => <exception_type>) {
25/// <catch_block>
26/// } catch exception (<catch_all_pattern>) {
27/// <catch_all_block>
28/// } catch panic (<panic_pattern>) {
29/// <catch_panic_block>
30/// } finally {
31/// <finally_block>
32/// }
33/// }
34/// ```
35///
36/// - **try block**: The primary code to execute.
37/// - **catch blocks**: Handle exceptions matching specific types.
38/// - **catch exception block** (optional): A generic handler for exceptions not caught by specific `catch` blocks.
39/// - **catch panic block** (optional): Handle non-exception panics.
40/// - **finally block** (optional): Executes unconditionally after the `try` block and any `catch` blocks.
41///
42/// ## Notes
43/// - at least 2 blocks have to be defined you cannot have a bare try {} expression
44/// - the try and catch blocks should both return the same type
45/// - the finally block should return () aka "unit"
46///
47/// # Features
48///
49/// - Matches exceptions based on type.
50/// - Catches generic exceptions with `catch exception`.
51/// - Handles panics using `catch panic`.
52/// - Ensures cleanup via an optional `finally` block.
53///
54/// # Usage
55///
56/// ## Handling specific exceptions
57/// ```
58/// #[derive(Debug)]
59/// struct MyErrorType;
60///
61/// fn some_function() -> Result<i32, MyErrorType> {
62/// Err(MyErrorType)
63/// }
64///
65/// let result = rust_try_catch::try_catch! {
66/// try {
67/// rust_try_catch::tri!(some_function())
68/// } catch (e => MyErrorType) {
69/// println!("Caught MyErrorType: {e:?}");
70/// -1
71/// }
72/// };
73/// assert_eq!(result, -1);
74/// ```
75///
76/// ## Catching all exceptions
77/// ```
78/// # fn another_function() {
79/// # rust_try_catch::throw("Haha I failed");
80/// # }
81///
82/// let result = rust_try_catch::try_catch! {
83/// try {
84/// // Code that might throw.
85/// another_function();
86/// 0
87/// } catch exception (e) {
88/// println!("Caught an exception: {:?}", e);
89/// -2
90/// }
91/// };
92/// assert_eq!(result, -2);
93/// ```
94///
95/// ## Handling panics
96/// ```
97/// let result = rust_try_catch::try_catch! {
98/// try {
99/// // Code that might panic.
100/// panic!("Unexpected error");
101/// 0
102/// } catch panic (e) {
103/// println!("Caught a panic: {:?}", e);
104/// -101
105/// }
106/// };
107/// assert_eq!(result, -101);
108/// ```
109///
110/// ## Using a finally block
111/// ```
112/// let mut cleanup = false;
113/// let result = rust_try_catch::try_catch! {
114/// try {
115/// // Code execution.
116/// 42
117/// } finally {
118/// cleanup = true;
119/// }
120/// };
121/// assert_eq!(result, 42);
122/// assert!(cleanup);
123/// ```
124///
125/// ## Combining handlers
126/// ```
127/// # #[derive(Debug)]
128/// # struct SpecificError;
129/// # let risky_operation = || rust_try_catch::throw(SpecificError);
130///
131/// let result = rust_try_catch::try_catch! {
132/// try {
133/// // Code execution.
134/// risky_operation();
135/// 0
136/// } catch (e => SpecificError) {
137/// println!("Caught SpecificError: {e:?}");
138/// -1
139/// } catch exception (e) {
140/// println!("Caught general exception: {e:?}");
141/// -2
142/// } catch panic (e) {
143/// println!("Caught a panic: {e:?}");
144/// -3
145/// } finally {
146/// println!("Cleanup actions here.");
147/// }
148/// };
149/// ```
150///
151/// # Notes
152///
153/// - The `catch panic` block is only invoked for panics unrelated to exceptions handled by the macro.
154/// - The `finally` block runs regardless of whether an exception or panic occurred.
155/// - Unhandled exceptions or panics will propagate out of the macro.
156///
157/// # Examples
158///
159/// ## No exception or panic (doesn't compile)
160/// ```compile_fail
161/// let result = rust_try_catch::try_catch! {
162/// try {
163/// 100
164/// }
165/// };
166/// assert_eq!(result, 100);
167/// ```
168///
169/// ## Exception without panic
170/// ```
171/// # use rust_try_catch::throw;
172///
173/// let result = rust_try_catch::try_catch! {
174/// try {
175/// throw("An error occurred");
176/// } catch (e => &'static str) {
177/// println!("Handled error: {}", e);
178/// 0
179/// }
180/// };
181/// assert_eq!(result, 0);
182/// ```
183///
184/// ## Panic recovery
185/// ```
186/// let result = rust_try_catch::try_catch! {
187/// try {
188/// panic!("Something went wrong!");
189/// } catch panic (e) {
190/// println!("Recovered from panic: {:?}", e);
191/// 1
192/// }
193/// };
194/// assert_eq!(result, 1);
195/// ```
196#[macro_export]
197macro_rules! try_catch {
198 {
199 try {
200 $($try_body: tt)*
201 } $(catch ($exception_name: pat => $exception_ty:ty) {
202 $($catch_body: tt)*
203 })* $(catch exception ($catch_all_exception_name: pat) {
204 $($catch_all_exception_body: tt)*
205 })? $(catch panic ($catch_panic_exception_name: pat) {
206 $($catch_panic_exception_body: tt)*
207 })? $(finally {
208 $($finally_body: tt)*
209 })?
210 } => {{
211 const {
212 let count = $crate::__count_blocks!(
213 {$($try_body)*}
214 $({$exception_name})*
215 $({$catch_all_exception_name})?
216 $({$catch_panic_exception_name})?
217 $({$($finally_body)*})?
218 );
219
220 if count < 2 {
221 ::core::panic!("Using try {{ /*code*/ }} is equivalent to a no-op")
222 }
223 }
224
225 struct FinallyDo<F: ::core::ops::FnOnce() -> ()>(::core::mem::ManuallyDrop<F>);
226 impl<F: ::core::ops::FnOnce()> Drop for FinallyDo<F> {
227 fn drop(&mut self) {
228 (unsafe { ::core::mem::ManuallyDrop::take(&mut self.0) })()
229 }
230 }
231
232 $(let _finally_guard = FinallyDo(::core::mem::ManuallyDrop::new(|| {
233 $($finally_body)*
234 }));)?
235
236 let fun = ::std::panic::AssertUnwindSafe(|| { $($try_body)* });
237 let val = match ::std::panic::catch_unwind(fun) {
238 Ok(res) => res,
239 Err(panic_payload) => 'ret_from_err: {
240 let mut exception = match panic_payload.downcast::<$crate::Thrown>() {
241 Ok(box_thrown) => box_thrown,
242 Err(normal_panic) => {
243 $({
244 let $catch_panic_exception_name = normal_panic;
245 break 'ret_from_err ({$($catch_panic_exception_body)*})
246 })?
247 #[allow(unreachable_code)]
248 ::std::panic::resume_unwind(normal_panic)
249 }
250 };
251
252 $(
253 match exception.source.downcast::<$exception_ty>() {
254 Ok(box_error) => {
255 let $exception_name: $exception_ty = *box_error;
256
257 break 'ret_from_err ({
258 $($catch_body)*
259 })
260 }
261 Err(other_error) => exception.source = other_error,
262 }
263 )*
264
265 $({
266 let $catch_all_exception_name = exception.source;
267 break 'ret_from_err ({$($catch_all_exception_body)*})
268 })?
269
270 #[allow(unreachable_code)]
271 ::std::panic::resume_unwind(exception)
272 }
273 };
274
275 val
276 }};
277}
278
279/// Unwraps a result or propagates its error as an exception.
280///
281/// tri! matches the given Result.
282/// In case of the Ok variant, the expression has the value of the wrapped value.
283/// In case of the Err variant, it retrieves the inner error, and calls throw on it.
284#[macro_export]
285macro_rules! tri {
286 ($expr: expr) => {
287 match ($expr) {
288 ::core::result::Result::Ok(val) => val,
289 ::core::result::Result::Err(err) => $crate::throw(err),
290 }
291 };
292}
293
294#[doc(hidden)]
295pub struct Thrown {
296 pub source: Box<dyn Any + Send>,
297 pub type_name: &'static str,
298 pub backtrace: Backtrace
299}
300
301/// Calling throw always results in a panic
302///
303/// for proper usage users must ensure that there is a function annotated with `rust_try_catch::throw_guard`
304/// up in the call chain
305pub fn throw<T: Any + Send + 'static>(x: T) -> ! {
306 std::panic::resume_unwind(Box::new(Thrown {
307 source: Box::new(x),
308 type_name: std::any::type_name::<T>(),
309 backtrace: Backtrace::force_capture()
310 }))
311}
312
313/// # Description
314/// wraps a function or closure, to prevent a thrown exception from propagating beyond them
315/// and turns unhandled exceptions to a panic
316///
317/// # Note
318/// thrown exceptions do not trigger the panic hook so if this isn't in the call chain before some code
319/// throws, the process might exit abruptly due to a panic with an unspecified load
320pub use rust_try_catch_macros::{throw_guard, closure_throw_guard};
321
322
323#[doc(hidden)]
324#[track_caller]
325pub fn __throw_driver<T>(main: impl FnOnce() -> T) -> T {
326 // help reduce size of throw_driver
327 #[inline(never)]
328 fn inner(f: &mut dyn FnMut()) {
329 if let Err(panic) = std::panic::catch_unwind(AssertUnwindSafe(f)) {
330 if let Some(Thrown { type_name, backtrace, .. }) = panic.downcast_ref() {
331 panic!("unhandled exception {type_name} at {backtrace}");
332 }
333
334 std::panic::resume_unwind(panic)
335 }
336 }
337
338 let mut main = Some(main);
339 let mut output = None;
340 inner(&mut || {
341 // Safety: inner runs `f` at most once
342 unsafe {
343 let main_fn = main.take().unwrap_unchecked();
344 // help skip destructor call
345 std::hint::assert_unchecked(output.is_none());
346 output = Some(main_fn())
347 }
348 });
349
350 // Safety: if inner returns that means the closure ran to completion
351 unsafe { output.unwrap_unchecked() }
352}
353
354#[doc(hidden)]
355#[macro_export]
356macro_rules! __count_blocks {
357 () => { 0 };
358 ({$($tt:tt)*} $($rest:tt)*) => {
359 1 + $crate::__count_blocks!($($rest)*)
360 }
361}