async-recursion

A Rust procedural macro that makes recursive async functions compile by boxing their returned Future.

Library
Cargo
v1.1.1
240stars
Apache License 2.0

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
45/100Fair
Development Activity36
Maintenance20
Community44
Maturity60
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
84/100Excellent
Architecture80
Code Quality84
Innovation78
Learning Curve92

async-recursion is a small but essential Rust procedural macro that lets you write recursive async fns. Rust’s compiler rejects a directly recursive async function because it would produce an infinitely sized Future; it even suggests this crate by name in the error message.

Applying the #[async_recursion] attribute automatically rewrites the function to return a boxed dyn Future, breaking the size cycle. It offers ?Send and Sync options to control the auto-trait bounds on the returned future, so it fits both single-threaded and multi-threaded async runtimes.

What You Get

  • The #[async_recursion] attribute macro for recursive async functions
  • Automatic boxing of the returned Future to satisfy the compiler
  • A ?Send option to drop the Send bound for single-threaded runtimes
  • A Sync option to add a Sync bound when needed
  • A tiny, dependency-light crate that just works with any async runtime

Common Use Cases

  • Recursively traversing trees or graphs in async code
  • Async parsers and interpreters that call themselves on sub-expressions
  • Divide-and-conquer async algorithms that await recursive calls
  • Walking nested data structures where each level performs async I/O

Under The Hood

Architecture

The crate is a procedural macro (proc-macro = true) that parses the annotated function with syn, then rewrites its signature to return Pin<Box<dyn Future<Output = ...> + Send + 'async_recursion>> and wraps the original body in an async block boxed with Box::pin. The Send/Sync auto-trait bounds are added or removed based on the ?Send and Sync attribute arguments, and lifetimes are threaded through so borrowed parameters remain valid across the boxed future. Code generation is emitted with quote.

Tech Stack

Pure Rust, ~31KB, built on the standard proc-macro trio: syn for parsing, quote for code generation, and proc-macro2 for token handling. It has no runtime dependencies and is compatible with any executor (Tokio, async-std, smol) since it only produces standard boxed futures.

Code Quality

Despite its small size the crate is extremely widely used (over 120 million downloads) and stable, with a focused test suite exercising the macro expansions across the Send/?Send/Sync variants and lifetime edge cases. Its longevity and adoption since 2019, plus being namechecked by the Rust compiler itself, make it a de-facto standard.

API Design

The API is about as ergonomic as possible: one attribute, no code changes at call sites, and two optional flags for the rare cases where the default Send/!Sync bounds don’t fit. The README’s fibonacci example and the compiler’s own suggestion make discovery and adoption effortless.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search