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}