loop_let/
lib.rs

1//! # loop_let
2//! 
3//! This crate provides a macro for a new control flow mechanism in Rust proposal `loop let`. 
4//! Which is a immutable loop pattern that is meant for tail call recursive algorithms earning
5//! tail call optimization for free.
6//! 
7//! This crate provides the `loop_let!` macro that implements the `loop let` pattern. The syntax
8//! is as follows:
9//! 
10//! ```rs no_run
11//! loop_let!((pattern) = (initial); {
12//!    // code ...
13//! });
14//! 
15//! loop_let!(fails (pattern) = (initial); {
16//!   // code ...
17//! });
18//! ```
19//! 
20//! The `pattern` is the pattern that will be matched against the input. The `initial` is the
21//! initial value of the input. The code block is the code that will be executed in every iteration
22//! of loop. It's important to notice that the code block must return either a `Continue` or
23//! a `Break`.
24//! 
25//! `Continue` is used to set the input value for the next iteration of the loop. `Break` is
26//! used to break the loop and return the value.
27//! 
28//! The `fails` keyword is used before the pattern when using fallible patterns. This way, the loop
29//! breaks once the pattern fails. By using the `fails` keyword, the `loop_let` macro will return
30//! `()` (this means you can only use `Break(())`). So it can't be used as an expression.
31//! 
32//! # Why?
33//! 
34//! The main purpose of this crate is to encourage the utilization of tail call recursive algorithms
35//! in Rust rather than the traditional recursion which relies on the compiler to optimize the code.
36//! 
37//! Some other earnings of using the `loop let` pattern in comparison to other tail call recursive
38//! approaches are:
39//! 
40//! - Support for pattern matching (even fallible patterns)
41//! - Loops as an expression
42//! - Avoids the need for `mut`
43//! - Avoids the need of tail call optimization at the compiler level
44//! - Increased readability
45//! 
46//! This pattern is used in Clojure with the loop/recur pattern where a loop might return a value
47//! or call itself with new arguments.
48//!  
49//! This is a proposal for a new control flow mechanism in Rust as a language construct `loop let`.
50//! 
51//! ```rs no_run
52//! // Proposed syntax (no need for `fails` keyword)
53//! loop let (pattern) = (initial) {
54//!     // ...
55//!     continue (new_input); // New syntax for continue
56//!     // ...
57//!     break (result);
58//! }
59//! ```
60
61/// Implementation of the `loop let` immutable loop pattern. This implements a loop that 
62/// is meant for tail call recursive algorithms earning tail call optimization for free.
63/// 
64/// The syntax is as follows:
65/// 
66/// ```rs no_run
67/// loop_let!((pattern) = (initial); { 
68///     // code ... 
69/// });
70/// ```
71/// The `pattern` is the pattern that will be matched against the input. The `initial` is the 
72/// initial value of the input. The code block is the code that will be executed in every iteration 
73/// of loop. It's important to notice that the code block must return either a `Continue` or 
74/// a `Break`.
75/// 
76/// `Continue` is used to set the input value for the next iteration of the loop. `Break` is 
77/// used to break the loop and return the value.
78/// 
79/// When using fallible patterns, the `fails` keyword must be used before the pattern. 
80/// This way, the loop breaks once the pattern fails. By using the `fails` keyword, the
81/// `loop_let` macro will return `()` (this means you can only use `Break(())`). So it can't 
82/// be used as an expression.
83/// 
84/// # Panics
85/// 
86/// If a fallible pattern is used without the `fails` keyword, the loop will panic once the
87/// pattern matching fails.
88/// 
89/// # Example
90/// ```rs
91/// use loop_let::loop_let;
92/// 
93/// let fib = loop_let!((n, curr, next) = (6, 0, 1); {
94///     if n == 0 {
95///        Break(curr)
96///     } else {
97///        Continue((n - 1, next, curr + next))
98///     }
99/// });
100/// 
101/// assert_eq!(fib, 8);
102/// ```
103#[macro_export]
104macro_rules! loop_let {
105    ($pattern:pat = $initial:expr; $body:block) => {
106        {
107            let mut input = $initial;
108
109            loop {
110                match input {
111                    $pattern => {
112                        use std::ops::ControlFlow::*;
113
114                        match $body {
115                            Continue(new_input) => {
116                                input = new_input;
117                            },
118                            Break(result) => break result,
119                        }
120                    },
121                    #[allow(unreachable_patterns)]
122                    _ => panic!("Pattern failed in `loop_let` macro, use `fails` keyword for fallible patterns as in `loop_let!(fails Ok(o) = result_fn(); {{ ... }})`"),
123                }
124            }
125        }
126    };
127
128    (fails $pattern:pat = $initial:expr; $body:block) => {
129        {
130            let mut input = $initial;
131
132            loop {
133                match input {
134                    $pattern => {
135                        use std::ops::ControlFlow::*;
136
137                        match $body {
138                            Continue(new_input) => {
139                                input = new_input;
140                            },
141                            Break(()) => break,
142                        }
143                    },
144                    _ => break,
145                }
146            }
147        }
148    };
149}
150
151#[cfg(test)]
152mod tests {
153    #[test]
154    fn test_loop_let() {
155        let result = loop_let!(x = 0; {
156            if x < 10 {
157                Continue(x + 1)
158            } else {
159                Break(x)
160            }
161        });
162
163        assert_eq!(result, 10);
164    }
165
166    #[test]
167    fn test_loop_let_fails() {
168        let result = loop_let!(fails Ok(_) = Err::<u8, &str>("error"); {
169            Continue(Err("error"))
170        });
171
172        assert_eq!(result, ());
173    }
174
175    #[test]
176    fn test_fibo() {
177        let fib = loop_let!((n, curr, next) = (6, 0, 1); {
178            if n == 0 {
179                Break(curr)
180            } else {
181                Continue((n - 1, next, curr + next))
182            }
183        });
184
185        assert_eq!(fib, 8);
186    }
187}