diff --git a/.gitignore b/.gitignore index ad67955..9546fb5 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e436d7c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "rust-tailcall" +version = "0.2.0" +edition = "2024" +authors = ["Codefonsi "] +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 = [] diff --git a/README.md b/README.md index a760f36..af030b0 100644 --- a/README.md +++ b/README.md @@ -1 +1,99 @@ -# rust-tailcall \ No newline at end of file +# 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 { + Continue(Rc TailCall>), + Done(T), +} +``` + + +## ✅ TailCall in basic example + +```rust +use rust_tailcall::TailCall; + +// Example usage: factorial function using TailCall +fn factorial(n: u64, acc: u64) -> TailCall { + 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 { + 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 \ No newline at end of file diff --git a/examples/basic.rs b/examples/basic.rs new file mode 100644 index 0000000..9a75b43 --- /dev/null +++ b/examples/basic.rs @@ -0,0 +1,15 @@ +use rust_tailcall::TailCall; + +// Example usage: factorial function using TailCall +fn factorial(n: u64, acc: u64) -> TailCall { + 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); +} diff --git a/examples/big_int.rs b/examples/big_int.rs new file mode 100644 index 0000000..771f3d7 --- /dev/null +++ b/examples/big_int.rs @@ -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 { + 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()); +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..f20afd5 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,104 @@ +/// Represents a tail call that can be either a continuation or a completed result. +pub enum TailCall { + /// A continuation that holds a deferred computation. + Continue(Box TailCall>), + /// A completed computation with a result. + Done(T), +} + +impl TailCall { + /// Applies the current step and returns the next `TailCall`. + /// + /// # Panics + /// Panics if called on a completed computation. + pub fn apply(&self) -> TailCall { + 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 +where + F: Fn() -> MutableTailCall, +{ + /// A continuation that holds a deferred computation. + Continue(F), + /// A completed computation with a result. + Done(T), +} + +impl MutableTailCall +where + F: Fn() -> MutableTailCall, +{ + /// Applies the current step and returns the next `TailCall`. + /// + /// # Panics + /// Panics if called on a completed computation. + pub fn apply(self) -> MutableTailCall { + 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; + } + } + } + } +} \ No newline at end of file