• Stars
    star
    138
  • Rank 264,508 (Top 6 %)
  • Language
    Rust
  • License
    Other
  • Created almost 5 years ago
  • Updated 5 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Safe, zero-cost tail recursion for stable Rust

Tailcall

Build Status Current Crates.io Version Docs

Tailcall is a library that adds safe, zero-cost tail recursion to stable Rust.

Eventually, it will be superseded by the become keyword.

Installation

Tailcall is distributed as a crate.

Add this to your Cargo.toml:

[dependencies]
tailcall = "0.1.5"

Usage

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

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.

Implementation

The core idea is to rewrite the function into a loop using the trampoline approach. Here is the (slightly reformatted) expansion for the gcd example above:

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.

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 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.

License

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

See LICENSE-APACHE and LICENSE-MIT, and COPYRIGHT for details.

More Repositories

1

active_record_distinct_on

Support for `DISTINCT ON` statements when querying with ActiveRecord
Ruby
33
star
2

no-titlebar-when-maximized

A GNOME Shell 👣 extension that hides the titlebar of maximized windows
JavaScript
33
star
3

flic

An unofficial Ruby implementation of the fliclib (use Flic IoT buttons in Ruby!)
Ruby
6
star
4

animator

A painless soft delete solution
Ruby
4
star
5

ruby_box

RubyBox allows the execution of untrusted Ruby code safely in a sandbox.
Ruby
4
star
6

paranoid

An experiment in truly rootless containerization for Linux
C
3
star
7

travis-matrix-badge

An unofficial matrix badge for TravisCI using Netlify Functions
JavaScript
3
star
8

the-windy-onion

A high-capacity Tor exit relay located in Chicago, IL
Shell
2
star
9

decl

Decl is a simple library designed to enable more declarative and unobtrusive JavaScript
TypeScript
2
star
10

meta.js

A source transformation tool that makes meta-programming in JavaScript a little more awesome.
JavaScript
2
star
11

hipaapotamus

Ruby
2
star
12

liberator

A simple program to detect and escape captive portals
C
1
star
13

otg.js

OscarTheGrouch.js is a lightweight garbage collector and object manager designed to be used with Meta.js.
JavaScript
1
star
14

wikipedia-word-counter

A small utility to get the top 100,000 words in a Wikipedia dump 📖
Rust
1
star
15

rails_admin_redactor

Adds the redactor edit to Rails Admin
Ruby
1
star
16

decl-classic

Decl is a simple library designed to enable more declarative and unobtrusive JavaScript
JavaScript
1
star
17

weak.js

Weak.js implements a variety of data structures to reduce the memory footprint of a JavaScript application. It depends on OscarTheGrouch.js.
JavaScript
1
star
18

firstlast-pg

`FIRST` and `LAST` aggregate functions for PostgreSQL
PLpgSQL
1
star