BackON, a crate designed to make retrying a built-in feature in Rust, has now reached v1.

This post aims to introduce the problems BackON addresses, why we need it, and what's next for the project.

What's BackON?

BackON is a rust retry library with the following features:

What sets BackON apart from other retry libraries is its API design.

Let's take an example:

use anyhow::Result;
use backon::ExponentialBuilder;
use backon::Retryable;

async fn fetch() -> Result<String> {
    Ok("hello, world!".to_string())
}

#[tokio::main]
async fn main() -> Result<()> {
    let content = fetch
        // Retry with exponential backoff
        .retry(ExponentialBuilder::default())
        // Sleep implementation, required if no feature has been enabled
        .sleep(tokio::time::sleep)
        // When to retry
        .when(|e| e.to_string() == "EOF")
        // Notify when retrying
        .notify(|err: &anyhow::Error, dur: Duration| {
            println!("retrying {:?} after {:?}", err, dur);
        })
        .await?;
    println!("fetch succeeded: {}", content);

    Ok(())
}

More examples can be found at backon::docs::examples.

BackON adds a retry function for all FnMut() -> impl Future<Output=Result<T>>, which returns a new Future that produces the same result. Users can control the retry strategy by providing a backoff, specifying the timing for retries, and defining the actions to take during retries.

BackON's vision is to make retrying as seamless as a built-in feature in Rust. It aims to become the default choice for retrying, similar to how serde is for serialization and deserialization in Rust.

Why we need BackON?

Compared to other existing retry libraries, BackON offers the following advantages:

  • Zero cost: No allocation occurs except in the sleep implementation.
  • WASM compatibility: BackON can be used within a WASM target.
  • no_std support: BackON can operate without the std; users only need to provide their own sleeper implementation.
  • No additional errors: BackON does not introduce new errors, so users do not need to adapt their error handling logic.

Due to rust's limitations, BackON cannot directly retry a function with arguments. Users need to create a closure to capture it as a FnMut() -> impl Future<Output=Result<T>> instead.

use anyhow::Result;
use backon::ExponentialBuilder;
use backon::Retryable;

async fn fetch(url: &str) -> Result<String> {
    Ok(reqwest::get(url).await?.text().await?)
}

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> {
    let content = (|| async { fetch("https://www.rust-lang.org").await })
        .retry(ExponentialBuilder::default())
        .await?;

    println!("fetch succeeded: {}", content);
    Ok(())
}

Users must be aware of this pattern; otherwise, they will encounter incomprehensible errors.

What's next?

After BackON reaches version 1.0, I promise not to disrupt users unless I decide to develop a 2.0 release.

I still have some great ideas I want to implement in v1.x:

The last and most important one is Call for maintainers.

I will continue to maintain this project and have no plans to stop working on it for now. However, it's crucial to bring more maintainers on board before things become unmanageable. This scenario has played out too often...

As described in the issue, there are many retry libraries left unmaintained on crates.io. I am determined not to let BackON become one of them. Please contact me if you are interested.

That's all. Welcome to try and evaluate BackON in your use cases and provide feedback. Thanks!