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}