retryx

Crates.ioretryx
lib.rsretryx
version1.0.0
created_at2025-12-17 09:36:05.122584+00
updated_at2025-12-17 09:36:05.122584+00
descriptionAsync-first retry and exponential backoff helper for Tokio-based Rust applications.
homepagehttps://github.com/shahnoorgit/retryx
repositoryhttps://github.com/shahnoorgit/retryx
max_upload_size
id1989832
size19,299
Shahnoor Mujawar (shahnoorgit)

documentation

https://docs.rs/retryx

README

retryx

Small, async-first retry & backoff helper for Tokio-based Rust services.

retryx gives you a fluent builder API to retry any async operation with fixed or exponential backoff, optional per-attempt timeouts, custom retry filters, and an on_retry hook for logging or metrics.

Features

  • Async-first: Designed for async/await and tokio.
  • Retry any async function: Works with Fn() -> Future<Output = Result<T, E>>.
  • Delay strategies: Fixed delay and exponential backoff with sane caps.
  • Per-attempt timeout: Fail attempts that overrun, using tokio::time::timeout.
  • Custom retry filters: Decide which errors are retryable.
  • on_retry hook: Integrate logging and metrics.
  • No macros, no unsafe: Simple, readable implementation.

Quick start

Add this to your Cargo.toml:

[dependencies]
retryx = "0.1"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }

Example: basic retry with exponential backoff

use retryx::retry;
use std::time::Duration;

#[derive(Debug, Clone)]
struct ApiError;

impl std::fmt::Display for ApiError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "api error")
    }
}

impl std::error::Error for ApiError {}

impl From<tokio::time::error::Elapsed> for ApiError {
    fn from(_: tokio::time::error::Elapsed) -> Self {
        ApiError
    }
}

async fn call_api() -> Result<String, ApiError> {
    // Your real API call goes here
    Ok("ok".to_string())
}

#[tokio::main]
async fn main() -> Result<(), ApiError> {
    let value = retry::<ApiError>()
        .times(3)
        .exponential()
        .timeout(Duration::from_secs(2))
        .run(|| async {
            call_api().await
        })
        .await?;

    println!("Got value: {value}");
    Ok(())
}

Example: custom filter and on_retry hook

use retryx::retry;
use std::time::Duration;

#[derive(Debug, Clone)]
enum ApiError {
    Timeout,
    RateLimited,
    Fatal,
}

impl std::fmt::Display for ApiError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ApiError::Timeout => write!(f, "timeout"),
            ApiError::RateLimited => write!(f, "rate limited"),
            ApiError::Fatal => write!(f, "fatal error"),
        }
    }
}

impl std::error::Error for ApiError {}

impl From<tokio::time::error::Elapsed> for ApiError {
    fn from(_: tokio::time::error::Elapsed) -> Self {
        ApiError::Timeout
    }
}

async fn call_api() -> Result<String, ApiError> {
    // Your real API call goes here
    Err(ApiError::Timeout)
}

#[tokio::main]
async fn main() -> Result<(), ApiError> {
    let result = retry::<ApiError>()
        .times(5)
        .fixed(Duration::from_millis(200))
        .when(|err| matches!(err, ApiError::Timeout | ApiError::RateLimited))
        .on_retry(|attempt, err, delay| {
            println!("retry {attempt}: {err}, next in {delay:?}");
        })
        .run(|| async {
            call_api().await
        })
        .await;

    println!("Final result: {:?}", result);
    Ok(())
}
Commit count: 0

cargo fmt