Tailcall
tailcall provides stack-safe tail calls on stable Rust.
It does this with an explicit trampoline runtime backed by a small stack-allocated internal thunk slot. The macro API rewrites a function into:
- a public wrapper that calls
tailcall::trampoline::run(...) - a hidden builder function that produces
tailcall::trampoline::Actionvalues
This is still a trampoline approach, but it no longer rewrites recursive calls into a local loop. Instead, each tail step is represented as a thunk and executed by the runtime.
Eventually, this crate may be superseded by the
become keyword.
Installation
Add this to your Cargo.toml:
[]
= "~2"
Usage
Mark a function with #[tailcall], and use tailcall::call! at each recursive tail-call site:
use tailcall;
assert_eq!;
The explicit tailcall::call! is part of the API now. Recursive calls are not rewritten
implicitly.
More Macro Examples
The macro also works well for stateful traversals over borrowed input:
use tailcall;
assert_eq!;
Most users should only need #[tailcall] plus tailcall::call!.
Macro-Based Mutual Recursion
Mutual recursion works through the macro too. Each participating function just needs
#[tailcall], and each tail-call site must use tailcall::call!:
use tailcall;
assert!;
assert!;
Methods
Methods in impl blocks work too, including recursive calls written with method syntax on
self:
use tailcall;
;
let parity = Parity;
assert!;
Mixed Recursion
Mixed recursion also works in a single #[tailcall] function. Only call sites wrapped in
tailcall::call! are trampoline-backed; plain recursive calls stay ordinary Rust calls:
use tailcall;
assert_eq!;
If only part of a larger algorithm is tail-recursive, it can still be cleaner to put that part in a helper and annotate the helper:
use tailcall;
assert_eq!;
assert_eq!;
This helper pattern is often the cleanest approach when one inner phase is tail-recursive and the rest of the algorithm is not.
Advanced: Direct Runtime
The runtime can also be used directly:
use trampoline;
The direct runtime is still useful as an escape hatch for advanced manual control. For example,
one function can skip separators while another reads digits from the same slice, passing control
back and forth through trampoline::Action.
Macro Expansion Shape
At a high level, this:
becomes roughly:
The exact expansion is different in edge cases, but this is the core model.
Limitations
Current macro limitations:
- Tail-call sites must use
tailcall::call! { path(args...) }ortailcall::call! { self.method(args...) }. - Function arguments must use simple identifier patterns.
?is not supported inside#[tailcall]functions on stable Rust. Usematchor explicit early returns instead.- Trait methods are not supported yet.
- In mixed recursion, only
tailcall::call!sites are trampoline-backed; plain recursive calls still use the native call stack. async fnandconst fnare not supported.
The runtime itself can be used directly if the macro is too restrictive for a particular use case.
Safety Notes
The thunk runtime uses unsafe code internally to type-erase FnOnce values into a fixed-size stack
slot. The current test suite includes:
- ordinary correctness tests
- stack-behavior tests
- destructor-behavior tests
- Miri runs over the runtime-oriented tests
Development
Common commands:
Release Process
This workspace publishes two crates:
tailcall-impltailcall
Release them together.
- Update the workspace version in the root Cargo.toml.
- Update the matching versions in the root
workspace.dependenciessection. - Update the installation snippet in this README if the major version changed.
- Run:
- Publish
tailcall-implfirst:
- Publish
tailcallaftertailcall-implis available on crates.io:
- Tag the release from
main:
Contributing
Bug reports and pull requests are welcome on GitHub.
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.