backon
Retry any Rust function, sync or async, with exponential, fibonacci, or constant backoff strategies chained directly onto the call.
Repository Health
Technical Analysis
BackON turns retry logic into a fluent extension method on ordinary Rust functions and closures. Calling .retry(ExponentialBuilder::default()) on any FnMut that returns a Result (or a future resolving to one) wraps it with configurable backoff, jitter, retry predicates, and notification hooks, without writing a manual retry loop or boxing futures.
The crate is split into three composable trait families: Backoff/BackoffBuilder for timing strategies (exponential, fibonacci, constant, or a custom iterator), Sleeper/BlockingSleeper for the actual delay mechanism (Tokio, WASM, futures-timer, Embassy, or blocking std::thread::sleep), and Retryable/BlockingRetryable for the orchestration layer that ties them together. Because each concern is a separate trait, swapping a backoff strategy or sleep implementation never touches the others.
BackON compiles under #![no_std] and targets WASM and embedded platforms in addition to standard async runtimes, with RetryableWithContext solving the common Rust ergonomics problem where a mutably-borrowed value can’t otherwise escape an FnMut closure across an .await boundary.
What You Get
- Two entry-point traits —
Retryablefor async functions andBlockingRetryablefor sync ones — plus context-preserving variants (RetryableWithContext,BlockingRetryableWithContext) for functions that borrow&mut self. - Three backoff builders (
ExponentialBuilder,FibonacciBuilder,ConstantBuilder) with jitter, min/max delay, and max-attempt controls, all built on a publicBackoffBuilder/Backofftrait pair you can implement yourself for custom strategies like an HTTPRetry-Afterheader. - Five pluggable sleeper implementations spanning Tokio, WASM (
gloo-timers),futures-timer, Embassy (embedded), and blockingstd::thread::sleep, selected via Cargo features. - A documented example catalogue (
docs::examples) covering plain closures,&self/&mut selfmethods, custom sleepers, andsqlxdatabase integration.
Common Use Cases
- Wrapping HTTP client calls to survive transient network failures without a custom retry loop.
- Adding backoff to database or queue reconnection logic in async services.
- Retrying flaky test assertions or CI steps that depend on external services.
- Building resilient no_std or embedded firmware that needs delayed retries without an OS scheduler.
Under The Hood
Architecture
BackON is a two-crate Cargo workspace: backon (the library) and backon-macros (proc-macro attribute helpers used only in its own dev-dependencies/tests). Inside backon/src, backoff/ defines the Backoff/BackoffBuilder traits (api.rs) plus three concrete strategies (exponential.rs, fibonacci.rs, constant.rs); sleep.rs/blocking_sleep.rs define Sleeper/BlockingSleeper and their cfg-gated adapters; and retry.rs/blocking_retry.rs implement the orchestration layer as a hand-written Future/state machine (using core::task::{Context, Poll} and the ready! macro) rather than async-trait, which is what makes the crate zero-cost and no_std-compatible. retry_core.rs centralizes shared config and default predicates consumed by both the plain and context-preserving retry paths, so a change there ripples through retry.rs and retry_with_context.rs alike. The three concerns — backoff timing, sleeping, and retry orchestration — are fully decoupled traits composed only at the call site.
Tech Stack
Pure Rust, edition 2024, with an aggressive MSRV of 1.85. The only unconditional dependency is fastrand (with std/no-std variants) for jitter; everything else — tokio (time feature), futures-timer, gloo-timers, embassy-time — is optional and feature-gated per target. Dev-dependencies (anyhow, reqwest, spin, sqlx with sqlite+tokio, wasm-bindgen-test) exercise the crate across blocking, async, database, and WASM scenarios. Tooling is a standard Cargo workspace with rustfmt.toml and taplo.toml (TOML formatting) plus GitHub Actions for CI and crates.io publishing.
Code Quality
Unit tests are colocated with implementation across the backoff and retry modules, and the crate root enforces #![deny(missing_docs)] and #![deny(unused_qualifications)], meaning every public item requires a doc comment — a strong, compiler-enforced documentation bar rather than a style convention. Errors are generic over the caller’s error type (E) with no internal panics observed in the retry path; behavior is expressed through Option/ControlFlow rather than exceptions. backon-macros additionally uses trybuild for compile-fail testing of its attribute macros.
API Design
The headline ergonomic win is that retry reads as a native language feature: your_fn.retry(ExponentialBuilder::default()).await requires no wrapper type construction. RetryableWithContext specifically addresses a well-known Rust pain point — an FnMut closure that captures &mut self can’t let that reference escape into an async block — by handing ownership back out alongside the result, a solution most comparable retry crates don’t offer. The same trait vocabulary spans sync, async, WASM, and no_std/embedded targets, which is unusually broad coverage for a crate this small.