Skip to main content

tailcall

Attribute Macro tailcall 

Source
#[tailcall]
Expand description

Transforms a function definition so that all recursive calls within the body are guaranteed to use a single stack frame (via tail recursion).

For simple free functions and inherent methods that only tail-call themselves directly, the macro lowers the item into an inline loop. More complex cases keep the hidden thunk-builder shape and run through tailcall::runtime::Thunk.

For methods, the optimized path aliases the receiver once, reuses the non-receiver arguments as mutable loop state, and rewrites each direct self tail call into “compute the next arguments, assign them, and continue”.

§Example

use tailcall::tailcall;

fn factorial(input: u64) -> u64 {
    #[tailcall]
    fn factorial_inner(accumulator: u64, input: u64) -> u64 {
        if input > 0 {
            tailcall::call! { factorial_inner(accumulator * input, input - 1) }
        } else {
            accumulator
        }
    }

    factorial_inner(1, input)
}

§Requirements

  • Tail-call sites must be written with tailcall::call! and left in tail form: tailcall::call! currently supports direct function paths and method syntax on self.
use tailcall::tailcall;
   
#[tailcall]
fn factorial(input: u64) -> u64 {
    if input > 0 {
        input * tailcall::call! { factorial(input - 1) }
//      ^^^^^^^ This is not allowed.
    } else {
        1
    }
}
use tailcall::tailcall;

#[tailcall]
fn factorial(input: u64) -> u64 {
    if input > 0 {
        1 + tailcall::call! { factorial(input - 1) }
//          ^^^^^^^^^^^^^^^ This is not allowed.
    } else {
        1
    }
}
  • Function arguments must use simple identifier patterns:
use tailcall::tailcall;

#[tailcall]
fn sum((left, right): (u64, u64)) -> u64 {
//      ^^^^^^^^^^^^^ Simple identifier arguments are required.
    left + right
}
  • Methods in impl blocks are supported, but trait methods are not:
use tailcall::tailcall;

trait Factorialable {
    fn factorial(self) -> Self {
        self.calc_factorial(1)
    }

    fn calc_factorial(self, accumulator: u64) -> u64;
}

impl Factorialable for u64 {
    #[tailcall]
    fn calc_factorial(self, accumulator: u64) -> u64 {
//                    ^^^^ Trait methods are not supported yet.
        if self > 0 {
            (self - 1).calc_factorial(self * accumulator)
        } else {
            accumulator
        }
    }
}
  • async fn and const fn are not supported:
use tailcall::tailcall;

#[tailcall]
async fn countdown(input: u64) -> u64 {
    if input > 0 {
        tailcall::call! { countdown(input - 1) }
    } else {
        0
    }
}
use tailcall::tailcall;

#[tailcall]
const fn countdown(input: u64) -> u64 {
    if input > 0 {
        tailcall::call! { countdown(input - 1) }
    } else {
        0
    }
}