├── .gitignore
├── .idea
├── .gitignore
├── misc.xml
├── modules.xml
├── rust-try-catch.iml
└── vcs.xml
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── LICENSE.Apache-2.0
├── LICENSE.MIT
├── README.MD
├── rust-try-catch-macros
├── Cargo.toml
└── src
│ ├── lib.rs
│ └── parse.rs
└── rust-try-catch
├── Cargo.lock
├── Cargo.toml
└── src
└── lib.rs
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 | # Datasource local storage ignored files
7 | /dataSources/
8 | /dataSources.local.xml
9 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | {}
4 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/rust-try-catch.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 4
4 |
5 | [[package]]
6 | name = "proc-macro2"
7 | version = "1.0.92"
8 | source = "registry+https://github.com/rust-lang/crates.io-index"
9 | checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
10 | dependencies = [
11 | "unicode-ident",
12 | ]
13 |
14 | [[package]]
15 | name = "quote"
16 | version = "1.0.37"
17 | source = "registry+https://github.com/rust-lang/crates.io-index"
18 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
19 | dependencies = [
20 | "proc-macro2",
21 | ]
22 |
23 | [[package]]
24 | name = "rust-try-catch"
25 | version = "0.1.0"
26 | dependencies = [
27 | "rust-try-catch-macros",
28 | ]
29 |
30 | [[package]]
31 | name = "rust-try-catch-macros"
32 | version = "0.1.0"
33 | dependencies = [
34 | "proc-macro2",
35 | "quote",
36 | "syn",
37 | ]
38 |
39 | [[package]]
40 | name = "syn"
41 | version = "2.0.90"
42 | source = "registry+https://github.com/rust-lang/crates.io-index"
43 | checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
44 | dependencies = [
45 | "proc-macro2",
46 | "quote",
47 | "unicode-ident",
48 | ]
49 |
50 | [[package]]
51 | name = "unicode-ident"
52 | version = "1.0.14"
53 | source = "registry+https://github.com/rust-lang/crates.io-index"
54 | checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
55 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | resolver = "2"
3 |
4 | members = ["rust-try-catch", "rust-try-catch-macros"]
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT OR Apache-2.0
2 |
--------------------------------------------------------------------------------
/LICENSE.Apache-2.0:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/LICENSE.MIT:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019-2022 Stephen Pryde and the thirtyfour contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
1 | # Rust Try Catch - Reinventing the nightmare!
2 |
3 | Welcome to **`rust-try-catch`**, the crate that dares to bring the spirit of Java's infamous `try-catch-finally` block to Rust's peaceful shores.
4 | Why settle for *Result* and *Option* when you can spice up your life with panics, exceptions, and an uncanny resemblance to the debugging hellscape you left behind in your old job?
5 |
6 | ---
7 |
8 | ## Features (or "Sins Against Rustaceans")
9 |
10 | - **Bring Back `throw` and `catch`**: Just when you thought the only `throw`ing and `catch`ing you do was in your high-schools baseball team, we decided to give it a whole new purpose: nightmares.
11 | - **Panic Recovery**: Because catching panics makes more sense than fixing your code, right?
12 | - **Generic Exception Handling**: Catch `anything`. Who needs type safety when you can have mystery bugs?
13 | - **Else Blocks**: Pretend you're writing Python.
14 | - **Finally Blocks**: Clean up after your disasters. Or don’t. We don’t judge.
15 | - **Readability? Nope.** It’s not a bug; it’s a *feature*. Embrace spaghetti syntax and callback hell in a language that fought so hard to avoid it.
16 |
17 | ---
18 |
19 | ## Why?
20 |
21 | ### Because Error Handling in Rust is Too Safe
22 | Rust’s Result and Option types are just too perfect.
23 | Developers are practically forced to think about errors and handle them responsibly. Where's the thrill of runtime crashes?
24 | Rust must embrace the unpredictability of try-catch so we can get back to debugging stack traces at 2 AM, like real programmers.
25 |
26 | ### We Miss Nested Pyramid Code
27 | Without try-catch, we’re stuck writing linear, readable error handling with .and_then() and the ? operator. UGH Gross!
28 | We want to go back to deeply nested code where `try { try { try { ... } catch { ... } } catch { ... } }` creates a delightful maze for future maintainers to explore.
29 |
30 | ### The World Needs More `catch exception (e)` Jokes
31 | In the try-catch world, you can slap a generic `catch exception (e)` block everywhere and call it "handling errors."
32 | It’s a rite of passage to spend hours debugging only to realize `catch` swallowed an error you never logged.
33 | Rust users deserve the same rite, don’t you think?
34 |
35 | ### Your life is already hard enough
36 | The `unwrap()` method is far too stigmatized in Rust.
37 | What if we could just shove all possible errors into a catch block and forget about them?
38 | Productivity will skyrocket when developers no longer worry about pesky things like whether their program works correctly.
39 |
40 | ### Rust Needs Drama
41 | rust-try-catch would introduce exciting debates about whether to use exceptions or results.
42 | These debates could rival the classic apples vs. oranges.
43 | Rust forums and Reddit threads are way too focused on productive discussions right now. Where’s the chaos?
44 |
45 | ---
46 |
47 | ## Getting Started (Warning: Regret Ahead)
48 |
49 | ### Add to `Cargo.toml`
50 |
51 | ```toml
52 | [dependencies]
53 | rust-try-catch = "0.1.0" # Or whatever version the world has suffered through.
54 | ```
55 |
56 | ---
57 |
58 | # Usage: The Gift That Keeps on Giving
59 |
60 |
61 | ## `try_catch!` Macro
62 | At its core, `try_catch!` is a macro designed to take Rust's Result-based error handling
63 | and replace it with a beautifully convoluted system of try, catch, else, finally, and questionable life choices.
64 |
65 | ### Syntax
66 | ```text
67 | try_catch! {
68 | try {
69 | // Your innocent code goes here.
70 | } catch (exception => Type) {
71 | // Caught an exception? Great. Now figure out what to do with it.
72 | } catch exception (name) {
73 | // Catch all other exceptions not previously caught.
74 | } catch panic (panic_info) {
75 | // For when you're feeling nostalgic about debugging segfaults.
76 | } else (res) {
77 | // Hooray! Nothing bad happened (yet).
78 | } finally {
79 | // Clean up whatever mess you've created.
80 | }
81 | }
82 | ```
83 |
84 | #### Explanation of Blocks:
85 | - `try`: Contains the code you're actually trying to run.
86 | - `catch (exception => Type)`: Specifically handles exceptions of type Type. Use this for precise error matching.
87 | - `catch exception (name)`: A catch-all for any exceptions that aren't caught by specific type matches. It's like the last line of defense.
88 | - `catch panic (panic_info)`: Handles panics (because, why not?).
89 | - `else`: Runs if the try block finished normally, but before the finally block.
90 | - `finally`: Ensures some cleanup happens, even if all else fails.
91 |
92 | ### Example (You Asked for It)
93 |
94 | ```
95 | use rust_try_catch::try_catch;
96 |
97 | let result = try_catch! {
98 | try {
99 | panic!("Oops!"); // Because Rust's strictness is for cowards.
100 | } catch panic (info) {
101 | println!("Panic caught: {:?}", info);
102 | -42 // Return values that make sense, or don't. We don't care.
103 | }
104 | };
105 | assert_eq!(result, -42);
106 | ```
107 |
108 | ---
109 |
110 | ## `tri!` Macro
111 |
112 | The little sibling of try_catch!, tri! unwraps a Result and throws an exception if it’s an Err.
113 | It’s like ?, but with extra regret.
114 |
115 | ### Example
116 |
117 | ```
118 | use rust_try_catch::{try_catch, tri};
119 |
120 | let result = try_catch! {
121 | try {
122 | tri!(Err::("Something went wrong"))
123 | } catch (e => &str) {
124 | println!("Handled error: {}", e);
125 | -1
126 | }
127 | };
128 | assert_eq!(result, -1);
129 | ```
130 |
131 | ---
132 |
133 | ## `throw` Function
134 |
135 | When you’re tired of meaningful error propagation, just throw an exception instead.
136 | Bonus points if you hide it under 20 layers of function calls.
137 |
138 | ### Example
139 |
140 | ```should_panic
141 | use rust_try_catch::throw;
142 |
143 | throw("Goodbye, world!"); // It's not just a panic; it's an *exceptional* panic.
144 | ```
145 |
146 | ---
147 |
148 | ## `throw_guard` and `closure_throw_guard`
149 |
150 | When you need to keep your code from completely imploding due to an unchecked throw, `throw_guard` and `closure_throw_guard` are here to wrap functions or closures in a layer of protection—or at least an illusion of it.
151 |
152 | The `throw_guard` procedural macro can be applied to functions to ensure that thrown exceptions do not escape their bounds.
153 | Use this when you're feeling particularly responsible.
154 |
155 | This works with async functions too
156 |
157 | ### Example
158 | ```should_panic
159 | use rust_try_catch::throw_guard;
160 |
161 | #[throw_guard]
162 | fn risky_function() {
163 | rust_try_catch::throw("Oops, I did it again!");
164 | }
165 |
166 | fn main() {
167 | // Instead of crashing, exceptions from risky_function are turned into panics.
168 | risky_function();
169 | }
170 | ```
171 |
172 |
173 | The `closure_throw_guard` macro wraps closures instead of functions. Perfect for when you want your lambda to crash gracefully.
174 | ### Example
175 | ```should_panic
176 | use rust_try_catch::closure_throw_guard;
177 |
178 | let guarded_closure = closure_throw_guard!(|| {
179 | rust_try_catch::throw("Not today!");
180 | });
181 |
182 | guarded_closure(); // Exception caught and re-thrown as a panic.
183 | ```
184 |
185 | ---
186 |
187 | # Advanced: Layering Bad Decisions
188 |
189 | What happens when you combine specific catches,
190 | generic exception handling, panic recovery, and a finally block? Chaos.
191 |
192 | ```
193 | use rust_try_catch::try_catch;
194 |
195 | let result = try_catch! {
196 | try {
197 | panic!("Let's see what happens!");
198 | } catch (e => &'static str) {
199 | println!("Caught a static str: {}", e);
200 | 0
201 | } catch exception (ex) {
202 | println!("Caught a generic exception: {:?}", ex);
203 | 1
204 | } catch panic (info) {
205 | println!("Caught a panic: {:?}", info);
206 | -1
207 | } else {
208 | println!("Didn't catch anything but a cold.");
209 | 100
210 | } finally {
211 | println!("Because even bad code needs cleanup.");
212 | }
213 | };
214 |
215 | assert_eq!(result, -1);
216 | ```
217 |
218 | # Why use this crate?
219 | 1. You’re feeling nostalgic for Java.
220 | 2. You’re bored and like chaos.
221 | 3. Rust's error handling is too predictable.
222 | 4. Your bug tracker is feeling lonely.
223 |
--------------------------------------------------------------------------------
/rust-try-catch-macros/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "rust-try-catch-macros"
3 | version = "0.1.0"
4 | edition = "2021"
5 | description = "try catch proc macros"
6 | license = "MIT OR Apache-2.0"
7 | repository = "https://github.com/Vrtgs/rust-try-catch"
8 |
9 | keywords = ["try", "catch", "try-catch", "hell", "nightmare"]
10 |
11 | [lib]
12 | proc-macro = true
13 |
14 | [dependencies]
15 | proc-macro2 = "1"
16 | quote = "1"
17 | syn = { version = "2", features = ["full"] }
--------------------------------------------------------------------------------
/rust-try-catch-macros/src/lib.rs:
--------------------------------------------------------------------------------
1 | use proc_macro::TokenStream as TokenStream1;
2 | use proc_macro2::TokenStream;
3 | use quote::{quote_spanned, ToTokens};
4 | use syn::{parse_macro_input, parse_quote, ExprClosure, ItemFn};
5 | use syn::spanned::Spanned;
6 | use crate::parse::FnOrClosure;
7 |
8 | mod parse;
9 |
10 |
11 | #[proc_macro_attribute]
12 | pub fn throw_guard(attr: TokenStream1, item: TokenStream1) -> TokenStream1 {
13 | if !attr.is_empty() {
14 | let message = format!("Unexpected attributes \"{attr}\"");
15 | return syn::Error::new(TokenStream::from(attr).span(), message)
16 | .into_compile_error()
17 | .into()
18 | }
19 |
20 | let tokens = match parse_macro_input!(item as FnOrClosure) {
21 | FnOrClosure::Function(func) => wrap_func(func),
22 | FnOrClosure::Closure(closure) => wrap_closure(closure),
23 | };
24 |
25 | tokens.into()
26 | }
27 |
28 |
29 | #[proc_macro]
30 | pub fn closure_throw_guard(item: TokenStream1) -> TokenStream1 {
31 | wrap_closure(parse_macro_input!(item as ExprClosure)).into()
32 | }
33 |
34 | fn throw_guard_body(async_ness: Option<&syn::Token![async]>, fn_body: TokenStream) -> TokenStream {
35 | let span = fn_body.span();
36 | match &async_ness {
37 | Some(_) => quote_spanned! { span=>
38 | {
39 | let mut fut = ::core::pin::pin!(async { #fn_body });
40 | ::core::future::poll_fn(move |cx| {
41 | ::rust_try_catch::__throw_driver(|| {
42 | ::core::future::Future::poll(::core::pin::Pin::as_mut(&mut fut), cx)
43 | })
44 | }).await
45 | }
46 | },
47 | None => quote_spanned! { span=> ::rust_try_catch::__throw_driver(|| #fn_body) },
48 | }
49 | }
50 |
51 | fn wrap_closure(closure: ExprClosure) -> TokenStream {
52 | if let Some(token) = closure.constness {
53 | return syn::Error::new(token.span, "can't drive try catch logic in const")
54 | .into_compile_error()
55 | }
56 | let body_tokens = throw_guard_body(closure.asyncness.as_ref(), closure.body.into_token_stream());
57 |
58 | let closure = ExprClosure {
59 | body: parse_quote! { #body_tokens },
60 | ..closure
61 | };
62 |
63 | closure.into_token_stream()
64 | }
65 |
66 | fn wrap_func(input: ItemFn) -> TokenStream {
67 | if let Some(token) = input.sig.constness {
68 | return syn::Error::new(token.span, "can't drive try catch logic in const")
69 | .into_compile_error()
70 | }
71 |
72 | if let Some(variadic) = input.sig.variadic {
73 | return syn::Error::new(variadic.span(), "using variadic arguments would cause UB!!!")
74 | .into_compile_error()
75 | }
76 |
77 | let outer_fn_body = throw_guard_body(input.sig.asyncness.as_ref(), input.block.into_token_stream());
78 |
79 | let new_fn = ItemFn {
80 | attrs: input.attrs,
81 | vis: input.vis,
82 | sig: input.sig,
83 | block: parse_quote!({ #outer_fn_body }),
84 | };
85 |
86 | new_fn.into_token_stream()
87 | }
--------------------------------------------------------------------------------
/rust-try-catch-macros/src/parse.rs:
--------------------------------------------------------------------------------
1 | use syn::{ExprClosure, ItemFn};
2 | use syn::parse::{Parse, ParseStream};
3 | use syn::parse::discouraged::Speculative;
4 |
5 | pub(super) enum FnOrClosure {
6 | Function(ItemFn),
7 | Closure(ExprClosure),
8 | }
9 |
10 | impl Parse for FnOrClosure {
11 | fn parse(input: ParseStream) -> syn::Result {
12 | let fork = input.fork();
13 | if let Ok(function) = fork.parse::() {
14 | input.advance_to(&fork);
15 | return Ok(FnOrClosure::Function(function));
16 | }
17 |
18 | if let Ok(closure) = input.parse::() {
19 | return Ok(FnOrClosure::Closure(closure))
20 | }
21 |
22 | Err(input.error("Expected either a function or closure"))
23 | }
24 | }
25 |
26 | #[cfg(test)]
27 | mod tests {
28 | use super::*;
29 |
30 | #[test]
31 | fn test_parsing() -> syn::Result<()> {
32 | let function_code = r#"
33 | fn example(a: i32) -> i32 {
34 | a + 1
35 | }
36 | "#;
37 |
38 | let closure_code = r#"
39 | |a: i32| a + 1
40 | "#;
41 |
42 | assert!(matches!(syn::parse_str(function_code)?, FnOrClosure::Function(_)));
43 | assert!(matches!(syn::parse_str(closure_code)?, FnOrClosure::Closure(_)));
44 |
45 | Ok(())
46 | }
47 | }
--------------------------------------------------------------------------------
/rust-try-catch/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 4
4 |
5 | [[package]]
6 | name = "rust-try-catch"
7 | version = "0.1.0"
8 |
--------------------------------------------------------------------------------
/rust-try-catch/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "rust-try-catch"
3 | version = "0.1.0"
4 | edition = "2021"
5 | license = "MIT OR Apache-2.0"
6 | authors = ["Vrtgs"]
7 | readme = "../README.md"
8 | description = "Rust Try Catch - Reinventing the nightmare!"
9 | repository = "https://github.com/Vrtgs/rust-try-catch"
10 | documentation = "https://docs.rs/rust-try-catch"
11 |
12 |
13 | keywords = ["try", "catch", "try-catch", "hell", "nightmare"]
14 |
15 | [dependencies]
16 | rust-try-catch-macros = { version = "0.1.0", path = "../rust-try-catch-macros" }
--------------------------------------------------------------------------------
/rust-try-catch/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![deny(missing_docs)]
2 |
3 | #![doc = include_str!("../README.md")]
4 |
5 | use std::any::Any;
6 | use std::backtrace::Backtrace;
7 | use std::panic::AssertUnwindSafe;
8 |
9 | #[cfg(not(panic = "unwind"))]
10 | compile_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 `else` block and `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, an `else` block runs if the `try` finishes normally,
18 | /// and a `finally` block can be specified for cleanup actions.
19 | ///
20 | /// # Syntax
21 | /// ```text
22 | /// try_catch! {
23 | /// try {
24 | ///
25 | /// } catch ( => ) {
26 | ///
27 | /// } catch exception () {
28 | ///
29 | /// } catch panic () {
30 | ///
31 | /// } else () {
32 | ///
33 | /// } finally {
34 | ///
35 | /// }
36 | /// }
37 | /// ```
38 | ///
39 | /// - **try block**: The primary code to execute.
40 | /// - **catch blocks**: Handle exceptions matching specific types.
41 | /// - **catch exception block** (optional): A generic handler for exceptions not caught by specific `catch` blocks.
42 | /// - **catch panic block** (optional): Handle non-exception panics.
43 | /// - **else block** (optional): Executes if the `try` block finishes normally.
44 | /// - **finally block** (optional): Executes unconditionally after the `try` block and any `catch` and/or `else` blocks.
45 | ///
46 | /// ## Notes
47 | /// - at least 2 blocks have to be defined you cannot have a bare try {} expression
48 | /// - the try block (or else block if there is one) should have the same type as all of the catch blocks
49 | /// - an else block is only allowed if there is at least one catch block
50 | /// - the finally block should return () aka "unit"
51 | ///
52 | /// # Features
53 | ///
54 | /// - Matches exceptions based on type.
55 | /// - Catches generic exceptions with `catch exception`.
56 | /// - Handles panics using `catch panic`.
57 | /// - Ensures cleanup via an optional `finally` block.
58 | ///
59 | /// # Usage
60 | ///
61 | /// ## Handling specific exceptions
62 | /// ```
63 | /// #[derive(Debug)]
64 | /// struct MyErrorType;
65 | ///
66 | /// fn some_function() -> Result {
67 | /// Err(MyErrorType)
68 | /// }
69 | ///
70 | /// let result = rust_try_catch::try_catch! {
71 | /// try {
72 | /// rust_try_catch::tri!(some_function())
73 | /// } catch (e => MyErrorType) {
74 | /// println!("Caught MyErrorType: {e:?}");
75 | /// -1
76 | /// }
77 | /// };
78 | /// assert_eq!(result, -1);
79 | /// ```
80 | ///
81 | /// ## Catching all exceptions
82 | /// ```
83 | /// # fn another_function() {
84 | /// # rust_try_catch::throw("Haha I failed");
85 | /// # }
86 | ///
87 | /// let result = rust_try_catch::try_catch! {
88 | /// try {
89 | /// // Code that might throw.
90 | /// another_function();
91 | /// 0
92 | /// } catch exception (e) {
93 | /// println!("Caught an exception: {:?}", e);
94 | /// -2
95 | /// }
96 | /// };
97 | /// assert_eq!(result, -2);
98 | /// ```
99 | ///
100 | /// ## Handling panics
101 | /// ```
102 | /// let result = rust_try_catch::try_catch! {
103 | /// try {
104 | /// // Code that might panic.
105 | /// panic!("Unexpected error");
106 | /// 0
107 | /// } catch panic (e) {
108 | /// println!("Caught a panic: {:?}", e);
109 | /// -101
110 | /// }
111 | /// };
112 | /// assert_eq!(result, -101);
113 | /// ```
114 | ///
115 | /// ## Using an else block
116 | /// ```
117 | /// let result = rust_try_catch::try_catch! {
118 | /// try {
119 | /// 42
120 | /// } catch panic (e) {
121 | /// println!("Caught a panic: {:?}", e);
122 | /// None
123 | /// } else (try_result) {
124 | /// Some(try_result)
125 | /// }
126 | /// };
127 | /// assert_eq!(result, Some(42));
128 | /// ```
129 | ///
130 | /// ## Using a finally block
131 | /// ```
132 | /// let mut cleanup = false;
133 | /// let result = rust_try_catch::try_catch! {
134 | /// try {
135 | /// // Code execution.
136 | /// 42
137 | /// } finally {
138 | /// cleanup = true;
139 | /// }
140 | /// };
141 | /// assert_eq!(result, 42);
142 | /// assert!(cleanup);
143 | /// ```
144 | ///
145 | /// ## Combining handlers
146 | /// ```
147 | /// # #[derive(Debug)]
148 | /// # struct SpecificError;
149 | /// # let risky_operation = || rust_try_catch::throw(SpecificError);
150 | ///
151 | /// let result = rust_try_catch::try_catch! {
152 | /// try {
153 | /// // Code execution.
154 | /// risky_operation();
155 | /// 0
156 | /// } catch (e => SpecificError) {
157 | /// println!("Caught SpecificError: {e:?}");
158 | /// -1
159 | /// } catch exception (e) {
160 | /// println!("Caught general exception: {e:?}");
161 | /// -2
162 | /// } catch panic (e) {
163 | /// println!("Caught a panic: {e:?}");
164 | /// -3
165 | /// } finally {
166 | /// println!("Cleanup actions here.");
167 | /// }
168 | /// };
169 | /// ```
170 | ///
171 | /// # Notes
172 | ///
173 | /// - The `catch panic` block is only invoked for panics unrelated to exceptions handled by the macro.
174 | /// - The `finally` block runs regardless of whether an exception or panic occurred.
175 | /// - Unhandled exceptions or panics will propagate out of the macro.
176 | ///
177 | /// # Examples
178 | ///
179 | /// ## No exception or panic (doesn't compile)
180 | /// ```compile_fail
181 | /// let result = rust_try_catch::try_catch! {
182 | /// try {
183 | /// 100
184 | /// }
185 | /// };
186 | /// assert_eq!(result, 100);
187 | /// ```
188 | ///
189 | /// ## Exception without panic
190 | /// ```
191 | /// # use rust_try_catch::throw;
192 | ///
193 | /// let result = rust_try_catch::try_catch! {
194 | /// try {
195 | /// throw("An error occurred");
196 | /// } catch (e => &'static str) {
197 | /// println!("Handled error: {}", e);
198 | /// 0
199 | /// }
200 | /// };
201 | /// assert_eq!(result, 0);
202 | /// ```
203 | ///
204 | /// ## Panic recovery
205 | /// ```
206 | /// let result = rust_try_catch::try_catch! {
207 | /// try {
208 | /// panic!("Something went wrong!");
209 | /// } catch panic (e) {
210 | /// println!("Recovered from panic: {:?}", e);
211 | /// 1
212 | /// }
213 | /// };
214 | /// assert_eq!(result, 1);
215 | /// ```
216 | #[macro_export]
217 | macro_rules! try_catch {
218 | {
219 | try {
220 | $($try_body: tt)*
221 | } $(catch ($exception_name: pat => $exception_ty:ty) {
222 | $($catch_body: tt)*
223 | })* $(catch exception ($catch_all_exception_name: pat) {
224 | $($catch_all_exception_body: tt)*
225 | })? $(catch panic ($catch_panic_exception_name: pat) {
226 | $($catch_panic_exception_body: tt)*
227 | })? $(else $(($try_result_name: pat))? {
228 | $($else_body: tt)*
229 | })? $(finally {
230 | $($finally_body: tt)*
231 | })?
232 | } => {{
233 | const {
234 | let count = $crate::__count_blocks!(
235 | {$($try_body)*}
236 | $({$exception_name})*
237 | $({$catch_all_exception_name})?
238 | $({$catch_panic_exception_name})?
239 | $({$($else_body)*})?
240 | $({$($finally_body)*})?
241 | );
242 |
243 | if count < 2 {
244 | ::core::panic!("Using try {{ /*code*/ }} is equivalent to a no-op")
245 | }
246 |
247 | let except_count = $crate::__count_blocks!(
248 | $({$exception_name})*
249 | $({$catch_all_exception_name})?
250 | $({$catch_panic_exception_name})?
251 | );
252 |
253 | let else_count = $crate::__count_blocks!(
254 | $({$($else_body)*})?
255 | );
256 |
257 | if except_count == 0 && else_count != 0 {
258 | ::core::panic!("With no except statements, an else block can just be moved to the end of the corresponding try block")
259 | }
260 | }
261 |
262 | struct FinallyDo ()>(::core::mem::ManuallyDrop);
263 | impl Drop for FinallyDo {
264 | fn drop(&mut self) {
265 | (unsafe { ::core::mem::ManuallyDrop::take(&mut self.0) })()
266 | }
267 | }
268 |
269 | $(let _finally_guard = FinallyDo(::core::mem::ManuallyDrop::new(|| {
270 | $($finally_body)*
271 | }));)?
272 |
273 | let fun = ::std::panic::AssertUnwindSafe(|| { $($try_body)* });
274 | let val = match ::std::panic::catch_unwind(fun) {
275 | Ok(res) => {
276 | $(let res = {
277 | $(let $try_result_name = res;)?
278 | $($else_body)*
279 | };)?
280 | res
281 | }
282 | Err(panic_payload) => 'ret_from_err: {
283 | let mut exception = match panic_payload.downcast::<$crate::Thrown>() {
284 | Ok(box_thrown) => box_thrown,
285 | Err(normal_panic) => {
286 | $({
287 | let $catch_panic_exception_name = normal_panic;
288 | break 'ret_from_err ({$($catch_panic_exception_body)*})
289 | })?
290 | #[allow(unreachable_code)]
291 | ::std::panic::resume_unwind(normal_panic)
292 | }
293 | };
294 |
295 | $(
296 | match exception.source.downcast::<$exception_ty>() {
297 | Ok(box_error) => {
298 | let $exception_name: $exception_ty = *box_error;
299 |
300 | break 'ret_from_err ({
301 | $($catch_body)*
302 | })
303 | }
304 | Err(other_error) => exception.source = other_error,
305 | }
306 | )*
307 |
308 | $({
309 | let $catch_all_exception_name = exception.source;
310 | break 'ret_from_err ({$($catch_all_exception_body)*})
311 | })?
312 |
313 | #[allow(unreachable_code)]
314 | ::std::panic::resume_unwind(exception)
315 | }
316 | };
317 |
318 | val
319 | }};
320 | }
321 |
322 | /// Unwraps a result or propagates its error as an exception.
323 | ///
324 | /// tri! matches the given Result.
325 | /// In case of the Ok variant, the expression has the value of the wrapped value.
326 | /// In case of the Err variant, it retrieves the inner error, and calls throw on it.
327 | #[macro_export]
328 | macro_rules! tri {
329 | ($expr: expr) => {
330 | match ($expr) {
331 | ::core::result::Result::Ok(val) => val,
332 | ::core::result::Result::Err(err) => $crate::throw(err),
333 | }
334 | };
335 | }
336 |
337 | #[doc(hidden)]
338 | pub struct Thrown {
339 | pub source: Box,
340 | pub type_name: &'static str,
341 | pub backtrace: Backtrace
342 | }
343 |
344 | /// Calling throw always results in a panic
345 | ///
346 | /// for proper usage users must ensure that there is a function annotated with `rust_try_catch::throw_guard`
347 | /// up in the call chain
348 | pub fn throw(x: T) -> ! {
349 | std::panic::resume_unwind(Box::new(Thrown {
350 | source: Box::new(x),
351 | type_name: std::any::type_name::(),
352 | backtrace: Backtrace::force_capture()
353 | }))
354 | }
355 |
356 | /// # Description
357 | /// wraps a function or closure, to prevent a thrown exception from propagating beyond them
358 | /// and turns unhandled exceptions to a panic
359 | ///
360 | /// # Note
361 | /// thrown exceptions do not trigger the panic hook so if this isn't in the call chain before some code
362 | /// throws, the process might exit abruptly due to a panic with an unspecified load
363 | pub use rust_try_catch_macros::{throw_guard, closure_throw_guard};
364 |
365 |
366 | #[doc(hidden)]
367 | #[track_caller]
368 | pub fn __throw_driver(main: impl FnOnce() -> T) -> T {
369 | // help reduce size of throw_driver
370 | #[inline(never)]
371 | fn inner(f: &mut dyn FnMut()) {
372 | if let Err(panic) = std::panic::catch_unwind(AssertUnwindSafe(f)) {
373 | if let Some(Thrown { type_name, backtrace, .. }) = panic.downcast_ref() {
374 | panic!("unhandled exception {type_name} at {backtrace}");
375 | }
376 |
377 | std::panic::resume_unwind(panic)
378 | }
379 | }
380 |
381 | let mut main = Some(main);
382 | let mut output = None;
383 | inner(&mut || {
384 | // Safety: inner runs `f` at most once
385 | unsafe {
386 | let main_fn = main.take().unwrap_unchecked();
387 | // since output = None
388 | // this helps skip destructor call
389 | std::ptr::write(&mut output, Some(main_fn()))
390 | }
391 | });
392 |
393 | // Safety: if inner returns that means the closure ran to completion
394 | unsafe { output.unwrap_unchecked() }
395 | }
396 |
397 | #[doc(hidden)]
398 | #[macro_export]
399 | macro_rules! __count_blocks {
400 | () => { 0 };
401 | ({$($tt:tt)*} $($rest:tt)*) => {
402 | 1 + $crate::__count_blocks!($($rest)*)
403 | }
404 | }
--------------------------------------------------------------------------------