Tailcall
tailcall lets you write deeply recursive functions in Rust without blowing the stack—on stable Rust.
It provides explicit, stack-safe tail calls using a lightweight trampoline runtime, with a macro that keeps usage ergonomic.
Installation
[]
= "~2"
Quick Example
use tailcall;
assert_eq!;
That’s the core API:
- mark the function with
#[tailcall] - wrap recursive tail calls with
tailcall::call!
When to Use This
tailcall is useful when:
- you want to write naturally recursive code without risking stack overflow
- converting to loops would make the code harder to read
- you’re working with mutual recursion or recursive traversals
- you want stack safety without nightly features
It may not be ideal when:
- a simple loop is clearer
- you need maximum performance (there is some trampoline overhead)
How It Works (Briefly)
Rust does not guarantee tail call optimization. Deep recursion can overflow the stack.
tailcall avoids this by using a trampoline:
- each recursive step returns a deferred computation (
Thunk) - the runtime repeatedly executes those steps in a loop
- no additional stack frames are created
The key operation is:
Thunk::bounce(...)— produces the next step instead of recursing
This turns recursion into iteration under the hood.
Macro Usage
Most users only need the macro.
Basic Pattern
Only calls wrapped in tailcall::call! are stack-safe.
Mutual Recursion
use tailcall;
Methods
use tailcall;
;
Mixed Recursion
Only tailcall::call! sites are trampoline-backed:
use tailcall;
Recommended Pattern: Tail-Recursive Helper
use tailcall;
Using the Runtime Directly
The macro is just a thin layer over Thunk.
A Thunk<T> is a deferred value from a computation.
You build a chain of steps, then execute it with .call().
Core constructors
Thunk::value(x)— final resultThunk::new(f)— deferred computation returning a valueThunk::bounce(f)— deferred computation returning anotherThunk(this is what enables stack safety)
Example
use Thunk;
Thunk::bounce ensures each step returns control to the runtime loop instead of growing the call stack.
What the Macro Generates
At a high level, this:
becomes:
- a wrapper that calls
.call() - a hidden builder that returns
Thunk<T>
So the macro:
- rewrites your function into a trampoline-compatible form
- leaves control flow and logic unchanged
Limitations
- Tail calls must use
tailcall::call! - Only simple argument patterns are supported
?is not supported inside#[tailcall]functions (usematch)- Trait methods are not supported
async fnandconst fnare not supported- Only
tailcall::call!sites are stack-safe in mixed recursion
Closure Size Limit
Each deferred closure is stored in a fixed-size inline slot (~48 bytes).
If a closure captures more than that, constructing the Thunk will panic at runtime.
Macro-generated helper thunks are subject to the same limit, so functions with enough arguments or
captured state can also exceed it.
Notes
This approach mirrors proposed language-level tail calls (e.g. become), and provides a practical solution on stable Rust today.
Development
License
Tailcall is distributed under the terms of both the MIT license and the Apache License (Version 2.0).
See LICENSE-APACHE, LICENSE-MIT, and COPYRIGHT for details.