Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ target
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
Cargo.lock
23 changes: 23 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "rust-tailcall"
version = "0.2.0"
edition = "2024"
authors = ["Codefonsi <info@codefonsi.com>"]
license = "MPL-2.0"
description = "TailCall, The crate is a rust implementation of The Art of Simplicity_Venkat Subramaniam."
repository = "https://github.com/codefonsi/rust-tailcall"
homepage = "https://github.com/codefonsi/rust-tailcall"
documentation = "https://docs.rs/rust-tailcall"
keywords = ["TailCall", "stack-overflow", "type-safe", "functional", "immutable"]
readme = "./README.md"
include = ["src/**/*", "Cargo.toml", "../../README.md", "LICENSE"]

[dependencies]

[dev-dependencies]
num-bigint = "0.4"
num-traits = "0.2"


[features]
default = []
100 changes: 99 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,99 @@
# rust-tailcall
# rust-tailcall

# TailCall Implementation in Rust

This document explains how to implement and use tail call optimization (TCO) using Rust's type system and functional style. The crate is a rust implementation of The Art of Simplicity_Venkat Subramaniam.

## 📖 What is Tail Call?

A **tail call** is a function call performed as the final action of a function. In languages without proper tail call optimization, deep recursion can cause a stack overflow.

Rust does not guarantee tail call optimization at the compiler level, but we can simulate it using enums and iteration.

---

## ✅ TailCall in Rust

We define a `TailCall` enum that represents two states:

1. `Continue`: A pending computation wrapped in a closure.
2. `Done`: A completed result.

```rust
enum TailCall<T> {
Continue(Rc<dyn Fn() -> TailCall<T>>),
Done(T),
}
```


## ✅ TailCall in basic example

```rust
use rust_tailcall::TailCall;

// Example usage: factorial function using TailCall
fn factorial(n: u64, acc: u64) -> TailCall<u64> {
if n == 0 {
TailCall::Done(acc)
} else {
TailCall::Continue(Box::new(move || factorial(n - 1, acc * n)))
}
}

fn main() {
let result = factorial(5, 1).invoke();
println!("Factorial result: {}", result);
}
```

## ✅ TailCall in bigint example

```rust
use num_bigint::BigUint;
use num_traits::{One, Zero};
use rust_tailcall::TailCall;

/// Computes the factorial of `n` using tail recursion.
///
/// # Arguments
/// * `n` - The number to compute the factorial for.
/// * `acc` - The accumulator holding the running result.
///
/// # Returns
/// A `TailCall` that will eventually produce the factorial of `n`.
pub fn factorial(n: BigUint, acc: BigUint) -> TailCall<BigUint> {
if n.is_zero() {
TailCall::Done(acc)
} else {
let next_n = &n - BigUint::one();
let next_acc = &acc * &n;
TailCall::Continue(Box::new(move || factorial(next_n.clone(), next_acc.clone())))
}
}

fn main() {
// Example 1: Normal case
let result = factorial(BigUint::from(5u32), BigUint::one()).invoke();
println!("Factorial of 5 is: {}", result);

// Example 2: Edge case where n = 0
let result_zero = factorial(BigUint::zero(), BigUint::one()).invoke();
println!("Factorial of 0 is: {}", result_zero);

// Example 3: Larger number to test stack safety and arbitrary precision
let large = BigUint::from(1000u32); // Reduced from 10000 for practicality
let result_large = factorial(large.clone(), BigUint::one()).invoke();
println!("Factorial of 1000 has {} digits", result_large.to_str_radix(10).len());
}
```


## 🔗 Helpful Links & Resources

* 📘 [The Art of Simplicity_Venkat Subramaniam](https://www.youtube.com/watch?v=AFZMI4y7Cuk&list=LL&index=2)


## 📜 License

* Mozilla Public License 2.0
15 changes: 15 additions & 0 deletions examples/basic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use rust_tailcall::TailCall;

// Example usage: factorial function using TailCall
fn factorial(n: u64, acc: u64) -> TailCall<u64> {
if n == 0 {
TailCall::Done(acc)
} else {
TailCall::Continue(Box::new(move || factorial(n - 1, acc * n)))
}
}

fn main() {
let result = factorial(5, 1).invoke();
println!("Factorial result: {}", result);
}
36 changes: 36 additions & 0 deletions examples/big_int.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use num_bigint::BigUint;
use num_traits::{One, Zero};
use rust_tailcall::TailCall;

/// Computes the factorial of `n` using tail recursion.
///
/// # Arguments
/// * `n` - The number to compute the factorial for.
/// * `acc` - The accumulator holding the running result.
///
/// # Returns
/// A `TailCall` that will eventually produce the factorial of `n`.
pub fn factorial(n: BigUint, acc: BigUint) -> TailCall<BigUint> {
if n.is_zero() {
TailCall::Done(acc)
} else {
let next_n = &n - BigUint::one();
let next_acc = &acc * &n;
TailCall::Continue(Box::new(move || factorial(next_n.clone(), next_acc.clone())))
}
}

fn main() {
// Example 1: Normal case
let result = factorial(BigUint::from(5u32), BigUint::one()).invoke();
println!("Factorial of 5 is: {}", result);

// Example 2: Edge case where n = 0
let result_zero = factorial(BigUint::zero(), BigUint::one()).invoke();
println!("Factorial of 0 is: {}", result_zero);

// Example 3: Larger number to test stack safety and arbitrary precision
let large = BigUint::from(1000u32); // Reduced from 10000 for practicality
let result_large = factorial(large.clone(), BigUint::one()).invoke();
println!("Factorial of 1000 has {} digits", result_large.to_str_radix(10).len());
}
104 changes: 104 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/// Represents a tail call that can be either a continuation or a completed result.
pub enum TailCall<T> {
/// A continuation that holds a deferred computation.
Continue(Box<dyn Fn() -> TailCall<T>>),
/// A completed computation with a result.
Done(T),
}

impl<T> TailCall<T> {
/// Applies the current step and returns the next `TailCall`.
///
/// # Panics
/// Panics if called on a completed computation.
pub fn apply(&self) -> TailCall<T> {
match self {
TailCall::Continue(f) => f(),
TailCall::Done(_) => panic!("Cannot apply on completed TailCall"),
}
}

/// Checks whether the computation is completed.
pub fn is_completed(&self) -> bool {
matches!(self, TailCall::Done(_))
}

/// Returns the result if the computation is completed.
pub fn result(&self) -> Option<&T> {
match self {
TailCall::Done(val) => Some(val),
_ => None,
}
}

/// Executes the computation by iterating until a completed result is found.
pub fn invoke(self) -> T {
let mut current = self;
loop {
match current {
TailCall::Continue(f) => {
current = f();
}
TailCall::Done(val) => {
return val;
}
}
}
}
}


/// Represents a tail call that can be either a continuation or a completed result.
/// Uses static dispatch by parameterizing over the function type `F`.
pub enum MutableTailCall<T, F>
where
F: Fn() -> MutableTailCall<T, F>,
{
/// A continuation that holds a deferred computation.
Continue(F),
/// A completed computation with a result.
Done(T),
}

impl<T, F> MutableTailCall<T, F>
where
F: Fn() -> MutableTailCall<T, F>,
{
/// Applies the current step and returns the next `TailCall`.
///
/// # Panics
/// Panics if called on a completed computation.
pub fn apply(self) -> MutableTailCall<T, F> {
match self {
MutableTailCall::Continue(f) => f(),
MutableTailCall::Done(_) => panic!("Cannot apply on completed TailCall"),
}
}

/// Checks whether the computation is completed.
pub fn is_completed(&self) -> bool {
matches!(self, MutableTailCall::Done(_))
}

/// Returns the result if the computation is completed.
pub fn result(&self) -> Option<&T> {
match self {
MutableTailCall::Done(val) => Some(val),
_ => None,
}
}

/// Executes the computation by iterating until a completed result is found.
pub fn invoke(mut self) -> T {
loop {
match self {
MutableTailCall::Continue(f) => {
self = f();
}
MutableTailCall::Done(val) => {
return val;
}
}
}
}
}