├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── recursive-proc-macro-impl ├── Cargo.toml ├── LICENSE ├── README.md └── src │ └── lib.rs ├── src └── lib.rs └── tests └── test.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | Cargo.lock 3 | .vscode -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "recursive" 3 | version = "0.1.1" 4 | edition = "2021" 5 | authors = ["Orson Peters "] 6 | description = "Easy recursion without stack overflows." 7 | license = "MIT" 8 | repository = "https://github.com/orlp/recursive" 9 | readme = "README.md" 10 | 11 | [dependencies] 12 | recursive-proc-macro-impl = { version = "=0.1.1", path = "recursive-proc-macro-impl" } 13 | stacker = "0.1" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2024, Orson R. L. Peters 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the “Software”), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Recursive 2 | 3 | With `recursive` you can easily make (indirectly) recursive functions without 4 | worrying about stack overflows by marking them as `#[recursive]`: 5 | 6 | ```rust 7 | use recursive::recursive; 8 | 9 | #[recursive] 10 | fn sum(nums: &[u64]) -> u64 { 11 | if let Some((head, tail)) = nums.split_first() { 12 | head + sum(tail) 13 | } else { 14 | 0 15 | } 16 | } 17 | ``` 18 | 19 | Functions marked with `#[recursive]` will automatically grow the stack size if 20 | it is too small when called. See the 21 | [crate docs](https://docs.rs/recursive/latest/) for details. 22 | 23 | ## License 24 | 25 | `recursive` is licensed under the MIT license. 26 | -------------------------------------------------------------------------------- /recursive-proc-macro-impl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "recursive-proc-macro-impl" 3 | version = "0.1.1" 4 | edition = "2021" 5 | authors = ["Orson Peters "] 6 | description = "Procedural macros for the recursive crate." 7 | license = "MIT" 8 | repository = "https://github.com/orlp/recursive" 9 | readme = "README.md" 10 | 11 | [lib] 12 | proc-macro = true 13 | 14 | [dependencies] 15 | quote = "1.0" 16 | syn = { version = "2.0", features = ["full"] } 17 | -------------------------------------------------------------------------------- /recursive-proc-macro-impl/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2024, Orson R. L. Peters 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the “Software”), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /recursive-proc-macro-impl/README.md: -------------------------------------------------------------------------------- 1 | This is a helper crate, please see [`recursive`](https://crates.io/crates/recursive) instead. -------------------------------------------------------------------------------- /recursive-proc-macro-impl/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This is a helper crate for the `recursive` crate, containing the procedural macro. 2 | 3 | use proc_macro::TokenStream; 4 | use quote::{quote, ToTokens}; 5 | use syn::{parse_macro_input, ItemFn, ReturnType, Type}; 6 | 7 | #[proc_macro_attribute] 8 | pub fn recursive(args: TokenStream, item: TokenStream) -> TokenStream { 9 | let arg_parser = syn::meta::parser(|meta| { 10 | Err(meta.error("#[recursive] does not have configuration options")) 11 | }); 12 | parse_macro_input!(args with arg_parser); 13 | 14 | let mut item_fn: ItemFn = 15 | syn::parse(item.clone()).expect("#[recursive] can only be applied to functions"); 16 | assert!( 17 | item_fn.sig.asyncness.is_none(), 18 | "#[recursive] does not support async functions" 19 | ); 20 | 21 | let block = item_fn.block; 22 | let ret = match &item_fn.sig.output { 23 | // impl trait is not supported in closure return type, override with 24 | // default, which is inferring. 25 | ReturnType::Type(_, typ) if matches!(**typ, Type::ImplTrait(_)) => ReturnType::Default, 26 | _ => item_fn.sig.output.clone(), 27 | }; 28 | let wrapped_block = quote! { 29 | { 30 | ::recursive::__impl::stacker::maybe_grow( 31 | ::recursive::get_minimum_stack_size(), 32 | ::recursive::get_stack_allocation_size(), 33 | move || #ret { #block } 34 | ) 35 | } 36 | }; 37 | item_fn.block = Box::new(syn::parse(wrapped_block.into()).unwrap()); 38 | item_fn.into_token_stream().into() 39 | } 40 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Rust has a problematic relationship with recursive functions, because functions that recurse 2 | //! deeply can overflow the stack, crashing your program. This crate makes it easy to remedy 3 | //! this problem by marking (indirectly) recursive functions as such: 4 | //! ```rust 5 | //! use recursive::recursive; 6 | //! 7 | //! #[recursive] 8 | //! fn sum(nums: &[u64]) -> u64 { 9 | //! if let Some((head, tail)) = nums.split_first() { 10 | //! head + sum(tail) 11 | //! } else { 12 | //! 0 13 | //! } 14 | //! } 15 | //! ``` 16 | //! 17 | //! The way this prevents stack overflows is by checking the size of the remaining stack at the 18 | //! start of each call to your function. If this size is under a boundary set by 19 | //! [`set_minimum_stack_size`] (by default 128 KiB), a new stack is allocated and execution 20 | //! continues on that stack. This new stack's size is set using [`set_stack_allocation_size`], which 21 | //! is 2 MiB by default. 22 | //! 23 | //! This crate works by wrapping your function body in a call to [`stacker::maybe_grow`]. If this 24 | //! crate is not flexible enough for your needs consider using [`stacker`] directly yourself. 25 | //! 26 | //! ## What are the downsides? 27 | //! 28 | //! This crate is **not** zero cost, but it is also not limited to simple tail recursion or direct 29 | //! recursion. However, in most cases the stack size test is very fast and almost always succeeds 30 | //! without needing to allocate. If your recursive algorithm is very performance-sensitive I would 31 | //! suggest rewriting it to an iterative version regardless. 32 | //! 33 | //! This crate only supports those platforms that [`stacker`] supports. The Rust compiler itself 34 | //! uses [`stacker`], so the platform you're compiling on should always be fine, but for more 35 | //! obscure targets see its documentation. 36 | //! 37 | //! ## Which functions should I mark as `#[recursive]`? 38 | //! 39 | //! Any function that directly calls itself should be marked as `#[recursive]`, unless you know for 40 | //! certain that the stack is sufficiently large for any inputs that function will be called with. 41 | //! If you are feeding untrusted input into a recursive function you should always mark it as 42 | //! `#[recursive]`. 43 | //! 44 | //! It is not necessary to mark every single function that can indirectly recurse as `#[recursive]`. 45 | //! As long as every possible cycle of function calls includes at least one function marked 46 | //! `#[recursive]` you will be protected against stack overflows due to recursion. 47 | 48 | use std::sync::atomic::{AtomicUsize, Ordering}; 49 | 50 | // These are referred to only by the procedural macro. 51 | #[doc(hidden)] 52 | pub mod __impl { 53 | #[allow(unused)] 54 | pub use stacker; 55 | } 56 | 57 | /// Marks a function to use an automatically growing segmented stack. 58 | /// 59 | /// See the [crate docs](crate) for details. 60 | pub use recursive_proc_macro_impl::recursive; 61 | 62 | static MINIMUM_STACK_SIZE: AtomicUsize = AtomicUsize::new(128 * 1024); 63 | static STACK_ALLOC_SIZE: AtomicUsize = AtomicUsize::new(2 * 1024 * 1024); 64 | 65 | /// This sets the minimum stack size that [`recursive`] requires. 66 | /// 67 | /// If a function tagged with `#[recursive]` is called when the remaining stack size is less than 68 | /// this value a new stack gets allocated. 69 | /// 70 | /// The default value if this function is never called is 128 KiB. 71 | pub fn set_minimum_stack_size(bytes: usize) { 72 | MINIMUM_STACK_SIZE.store(bytes, Ordering::Relaxed); 73 | } 74 | 75 | /// Returns the value set by [`set_minimum_stack_size`]. 76 | pub fn get_minimum_stack_size() -> usize { 77 | MINIMUM_STACK_SIZE.load(Ordering::Relaxed) 78 | } 79 | 80 | /// When a new stack gets allocated it will get allocated with this size. 81 | /// 82 | /// The default value if this function is never called is 2 MiB. 83 | pub fn set_stack_allocation_size(bytes: usize) { 84 | STACK_ALLOC_SIZE.store(bytes, Ordering::Relaxed); 85 | } 86 | 87 | /// Returns the value set by [`set_stack_allocation_size`]. 88 | pub fn get_stack_allocation_size() -> usize { 89 | STACK_ALLOC_SIZE.load(Ordering::Relaxed) 90 | } 91 | -------------------------------------------------------------------------------- /tests/test.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | use recursive::recursive; 4 | 5 | #[recursive] 6 | fn sum(nums: &[u64]) -> u64 { 7 | if let Some((head, tail)) = nums.split_first() { 8 | head + sum(tail) 9 | } else { 10 | 0 11 | } 12 | } 13 | 14 | #[recursive] 15 | fn dyn_ret(b: bool, x: T, y: U) -> Box 16 | where 17 | T: Display + 'static, 18 | U: Display + 'static, 19 | { 20 | if b { 21 | Box::new(x) 22 | } else { 23 | Box::new(y) 24 | } 25 | } 26 | 27 | #[recursive] 28 | fn impl_ret(b: bool, x: T, y: T) -> impl Display 29 | where 30 | T: Display, 31 | { 32 | if b { 33 | Box::new(x) 34 | } else { 35 | Box::new(y) 36 | } 37 | } 38 | 39 | #[recursive] 40 | fn no_ret(x: &mut u32) { 41 | *x *= 10; 42 | } 43 | 44 | #[recursive] 45 | fn mut_arg(mut x: u32) -> u32 { 46 | x *= 10; 47 | x 48 | } 49 | 50 | #[test] 51 | fn test_sum() { 52 | let n = 10_000_000; 53 | let v: Vec = (0..n).collect(); 54 | assert_eq!(sum(&v), 49999995000000); 55 | } 56 | 57 | #[test] 58 | fn test_dyn_ret() { 59 | assert_eq!("10", format!("{}", dyn_ret(true, 10, "20"))); 60 | assert_eq!("20", format!("{}", dyn_ret(false, 10, "20"))); 61 | } 62 | 63 | #[test] 64 | fn test_impl_ret() { 65 | assert_eq!("10", format!("{}", impl_ret(true, 10, 20))); 66 | assert_eq!("20", format!("{}", impl_ret(false, 10, 20))); 67 | } 68 | 69 | #[test] 70 | fn test_mut_arg() { 71 | assert_eq!(100, mut_arg(10)); 72 | } 73 | 74 | #[test] 75 | fn test_no_ret() { 76 | let mut x = 42; 77 | no_ret(&mut x); 78 | assert_eq!(x, 420); 79 | } 80 | --------------------------------------------------------------------------------