├── .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 | } --------------------------------------------------------------------------------