tramp/lib.rs
1//! This crate provides some facilities for recursing in Rust.
2//!
3//! # Motivation
4//! Most iterative problems can be solved using recursion instead of loops.
5//! In fact, when you start learning functional programming, you will see
6//! the possibility of solving a given problem using recursion. If the language
7//! does not optimize away some kinds of recursion, programming functionally
8//! may be hard, because stack overflow may happen eventually if a big case of
9//! recursion happens.
10//!
11//! A common optimization is the so called "tail call optimization", "tail
12//! call reduction" or any similar name. First, let's understand tail call.
13//! Tail call is basically a function found in "tail" position of a function
14//! body. The tail position means the call is the last thing done in the
15//! function body. Optimizing it away means that, instead of generating a
16//! new stack frame for the called function, the compiler will reuse the
17//! current frame for the called function. In fact, the call may be turned into
18//! a loop. But it still is recursion, because the programmer did not write a
19//! loop. It's just an optimization.
20//!
21//! Currently, Rust does not provide any language item to optimize the tail
22//! call. There is a "promised" feature named `become`. `become`, for now,
23//! is just a reserved identifer, but the intention is that it acts like a
24//! return, but does a tail call reduction. Well, there is no prevision for
25//! when this is implemented or if it ever will be implemented. So, we decided
26//! to create a library allowing the programmer to write optimized recursion
27//! with tail calls.
28//!
29//! # Example
30//! Take a look in this factorial function:
31//! ```rust
32//! fn fac(n: u128) -> u128 {
33//! if n > 1 {
34//! n * fac(n - 1)
35//! } else {
36//! n
37//! }
38//! }
39//! ```
40//! Clearly, the function above is not tail recursive. After the recursive
41//! call, we still have to multiply the result by `n`. However, there is a
42//! well-known version of this function which takes an extra argument: an
43//! accumulator. Take a look:
44//! ```rust
45//! fn fac(n: u128) -> u128 {
46//! //!
47//! fn fac_with_acc(n: u128, acc: u128) -> u128 {
48//! if n > 1 {
49//! fac_with_acc(n - 1, acc * n)
50//! } else {
51//! 1
52//! }
53//! }
54//!
55//! fac_with_acc(n, 1)
56//! }
57//! ```
58//! The function `fac_with_acc` is tail recursive. There is no job to be done
59//! after the call to itself. There is only one problem: Rust does not do any
60//! tail call optimization (yet). Now, the crate `tramp` does its job. We
61//! implemented in this crate a trampoline. A trampoline simulates a tail
62//! call optimization by calling a function which might return another function
63//! (which will be called again) or a computed result. The trampoline will
64//! keep calling in a loop the returned function, and whenever the function
65//! returns a computed value instead of a function, the trampoline returns the
66//! value. Take a look in the example below.
67//!
68//! ```rust
69//! #[macro_use] extern crate tramp;
70//!
71//! use tramp::{tramp, Rec};
72//!
73//! fn factorial(n: u128) -> u128 {
74//! fn fac_with_acc(n: u128, acc: u128) -> Rec<u128> {
75//! if n > 1 {
76//! rec_call!(fac_with_acc(n - 1, acc * n))
77//! } else {
78//! rec_ret!(acc)
79//! }
80//! }
81//!
82//! tramp(fac_with_acc(n, 1))
83//! }
84//!
85//! assert_eq!(factorial(5), 120);
86//! ```
87#![no_std]
88extern crate alloc;
89use alloc::boxed::Box;
90use core::fmt;
91
92/// A single recursive-function result with static lifetime.
93pub type Rec<T> = BorrowRec<'static, T>;
94
95/// A single borrowed recursive-function result.
96#[derive(Debug)]
97pub enum BorrowRec<'a, T> {
98 /// This variant is returned when the function is done; i.e. this the
99 /// result of the computation.
100 Ret(T),
101 /// This variant is returned when the function is about to call itself
102 /// or another in a tail position (i.e. there is no job after the call),
103 /// generally indicates recursion.
104 Call(Thunk<'a, BorrowRec<'a, T>>),
105}
106
107trait FnThunk {
108 type Out;
109
110 fn call_boxed(self: Box<Self>) -> Self::Out;
111}
112
113/// A delayed computation. This can be used in lazy evaluation environments.
114/// Also, it is used to delay a tail call and emulate TCO (tail call
115/// optimization).
116pub struct Thunk<'a, T> {
117 fun: Box<dyn FnThunk<Out = T> + 'a>,
118}
119
120impl<T, F> FnThunk for F
121where
122 F: FnOnce() -> T,
123{
124 type Out = T;
125
126 fn call_boxed(self: Box<Self>) -> T {
127 (*self)()
128 }
129}
130
131impl<'a, T> Thunk<'a, T> {
132 /// Creates a new thunk from the given function. Probably you will end up
133 /// passing closures to this function.
134 pub fn new(fun: impl FnOnce() -> T + 'a) -> Self {
135 Self {
136 fun: Box::new(fun),
137 }
138 }
139
140 /// Computes the result of this thunk, i.e. forces evaluation to happen.
141 pub fn compute(self) -> T {
142 self.fun.call_boxed()
143 }
144}
145
146impl<'a, T> fmt::Debug for Thunk<'a, T> {
147 fn fmt(&self, fmtr: &mut fmt::Formatter) -> fmt::Result {
148 write!(fmtr, "thunk {} ptr: {:?} {}", '{', &*self.fun as *const _, '}')
149 }
150}
151
152/// Given an input which of type `BorrowRec`, this function performs
153/// a trampoline over the value. While `Rec::Call(thunk)` is returned,
154/// this function will keep evauating `thunk`. Whenever `Rec::Done(x)` is
155/// found, `x` is returned.
156pub fn tramp<'a, T>(mut res: BorrowRec<'a, T>) -> T {
157 loop {
158 match res {
159 BorrowRec::Ret(x) => break x,
160 BorrowRec::Call(thunk) => res = thunk.compute(),
161 }
162 }
163}
164
165/// Turns a (probably recursive) tail call into a return value of
166/// type `Rec`. The variant created is `Rec::Call`.
167/// Given an expression `x` of type `T`, then `rec_call!(x)` has type `Rec<T>`.
168/// It is equivalent to, given a fictional attribute "optimize_tail_call",
169/// `#[optimize_tail_call] return x` transforming `T` into `Rec<T>`.
170#[macro_export]
171macro_rules! rec_call {
172 ($call:expr) => {
173 return $crate::BorrowRec::Call($crate::Thunk::new(move || $call));
174 };
175}
176
177/// Returns a value from a `Rec`-function. This means the recursion is done.
178/// Given an expression `x` of type `T`, then `rec_ret!(x)` has type `Rec<T>`.
179/// It is equivalent to `return x` transforming `T` into `Rec<T>`.
180#[macro_export]
181macro_rules! rec_ret {
182 ($val:expr) => {
183 return $crate::BorrowRec::Ret($val);
184 };
185}