An open API service indexing awesome lists of open source software.

https://github.com/alecdotninja/tailcall

Safe, zero-cost tail recursion for stable Rust
https://github.com/alecdotninja/tailcall

recursion rust rust-lang tail-recursion tailcall

Last synced: 11 months ago
JSON representation

Safe, zero-cost tail recursion for stable Rust

Awesome Lists containing this project

README

          

# Tailcall

[![CI](https://github.com/alecdotninja/tailcall/actions/workflows/ci.yml/badge.svg)](https://github.com/alecdotninja/tailcall/actions/workflows/ci.yml)
[![Current Crates.io Version](https://img.shields.io/crates/v/tailcall.svg)](https://crates.io/crates/tailcall)
[![Docs](https://docs.rs/tailcall/badge.svg)](https://docs.rs/tailcall)

Tailcall is a library that adds safe, zero-cost [tail recursion](https://en.wikipedia.org/wiki/Tail_call) to stable Rust.

Eventually, it will be superseded by the [`become` keyword](https://internals.rust-lang.org/t/pre-rfc-explicit-proper-tail-calls/3797/16).

## Installation

Tailcall is distributed as a [crate](https://crates.io/crates/tailcall).

Add this to your `Cargo.toml`:

```toml
[dependencies]
tailcall = "~1"
```

## Usage

Add the `tailcall` attribute to functions which you would like to use tail recursion:

```rust
use tailcall::tailcall;

#[tailcall]
fn gcd(a: u64, b: u64) -> u64 {
if b == 0 {
a
} else {
gcd(b, a % b)
}
}
```

For more detailed information (including some limitations), please see [the docs](https://docs.rs/tailcall).

## Implementation

The core idea is to rewrite the function into a loop using the [trampoline approach](https://en.wikipedia.org/wiki/Tail_call#Through_trampolining).
Here is the (slightly reformatted) expansion for the `gcd` example above:

```rust
fn gcd(a: u64, b: u64) -> u64 {
tailcall::trampoline::run(
#[inline(always)] |(a, b)| {
tailcall::trampoline::Finish({
if b == 0 {
a
} else {
return tailcall::trampoline::Recurse((b, a % b))
}
})
},
(a, b),
)
}
```

You can view the exact expansion for the `tailcall` macro in your use-case with `cargo expand`.

## Development

Development dependencies, testing, documentation generation, packaging, and distribution are all managed via [Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html).

After checking out the repo, run `cargo test` to verify the test suite.
The latest documentation can be generated with `cargo doc`.
Before commiting, please make sure code is formatted canonically with `cargo fmt` and passes all lints with `cargo clippy`.
New versions are released to [crates.io](https://crates.io/crates/tailcall) with `cargo publish`.

## Benchmarks

There are a few benchmarks available; currently the benchmarks demonstrate that for
single-function tail-recursion, performance is the same as using a loop. Mutual
recursion runs, but suffers penalties.

```
$ cargo bench
Finished bench [optimized] target(s) in 0.05s
Running target/release/deps/tailcall-b55b2bddb07cb046

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

Running target/release/deps/bench-b8ab29e7ebef8d8d

running 4 tests
test bench_oddness_boom ... bench: 6 ns/iter (+/- 0)
test bench_oddness_loop ... bench: 6 ns/iter (+/- 0)
test bench_oddness_mutrec ... bench: 4,509,915 ns/iter (+/- 7,095,455)
test bench_oddness_rec ... bench: 3 ns/iter (+/- 0)

test result: ok. 0 passed; 0 failed; 0 ignored; 4 measured; 0 filtered out
```

If the optimization level is set to zero so that `bench_oddness_boom` isn't cleverly
optimized away, it blows the stack as expected:

```
$ RUSTFLAGS="-C opt-level=0" cargo bench _boom
Finished bench [optimized] target(s) in 0.05s
Running target/release/deps/tailcall-b55b2bddb07cb046

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

Running target/release/deps/bench-b8ab29e7ebef8d8d

running 1 test

thread 'main' has overflowed its stack
fatal runtime error: stack overflow
```

In fact the same occurs when running `RUSTFLAGS="-C opt-level=0" cargo bench _mutrec`
, indicating mutual recursion can also blow the stack, but the `loop` and tailrec-enabled
single-function, tail-recursive functions enjoy TCO:

```
$ RUSTFLAGS="-C opt-level=0" cargo bench _loop
Finished bench [optimized] target(s) in 0.06s
Running target/release/deps/tailcall-b55b2bddb07cb046

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

Running target/release/deps/bench-b8ab29e7ebef8d8d

running 1 test
test bench_oddness_loop ... bench: 4,514,730 ns/iter (+/- 7,498,984)

test result: ok. 0 passed; 0 failed; 0 ignored; 1 measured; 3 filtered out

$ RUSTFLAGS="-C opt-level=0" cargo bench _rec
Finished bench [optimized] target(s) in 0.05s
Running target/release/deps/tailcall-b55b2bddb07cb046

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

Running target/release/deps/bench-b8ab29e7ebef8d8d

running 1 test
test bench_oddness_rec ... bench: 22,416,962 ns/iter (+/- 16,083,896)

test result: ok. 0 passed; 0 failed; 0 ignored; 1 measured; 3 filtered out
```

## Contributing

Bug reports and pull requests are welcome on [GitHub](https://github.com/alecdotninja/tailcall).

## License

Tailcall is distributed under the terms of both the MIT license and the Apache License (Version 2.0).

See [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT), and [COPYRIGHT](COPYRIGHT) for details.