rust
49 lines · 8 steps
Async retry with exponential backoff in Rust
A generic async helper retries a fallible operation, backing off exponentially with jitter until it succeeds or runs out of attempts.
Explained by
highlit
1use std::time::Duration;
2use tokio::time::sleep;
3
4#[derive(Debug)]
5pub struct RetryConfig {
6 pub max_attempts: u32,
7 pub base_delay: Duration,
8 pub max_delay: Duration,
9}
10
11impl Default for RetryConfig {
12 fn default() -> Self {
13 Self {
14 max_attempts: 5,
15 base_delay: Duration::from_millis(200),
16 max_delay: Duration::from_secs(30),
17 }
18 }
19}
20
21pub async fn retry_with_backoff<F, Fut, T, E>(
22 config: &RetryConfig,
23 mut operation: F,
24) -> Result<T, E>
25where
26 F: FnMut() -> Fut,
27 Fut: std::future::Future<Output = Result<T, E>>,
28 E: std::fmt::Display,
29{
30 let mut attempt = 0;
31
32 loop {
33 attempt += 1;
34 match operation().await {
35 Ok(value) => return Ok(value),
36 Err(err) if attempt >= config.max_attempts => {
37 tracing::error!(attempt, %err, "operation failed, exhausted retries");
38 return Err(err);
39 }
40 Err(err) => {
41 let exp = config.base_delay.saturating_mul(1 << (attempt - 1));
42 let delay = exp.min(config.max_delay);
43 let jitter = Duration::from_millis(fastrand::u64(0..=100));
44 tracing::warn!(attempt, ?delay, %err, "retrying after backoff");
45 sleep(delay + jitter).await;
46 }
47 }
48 }
49}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Exponential backoff with a capped maximum and random jitter spreads out retries and avoids thundering-herd retries.
- 2Generic bounds over a future-returning closure let one helper wrap any async fallible operation.
- 3Saturating arithmetic and a max clamp keep the delay calculation safe from overflow at high attempt counts.
Related explainers
javascript
const express = require('express'); const { createProxyMiddleware, fixRequestBody } = require('http-proxy-middleware'); const router = express.Router();
Building an API gateway proxy in Express
reverse-proxy
middleware
api-gateway
Intermediate
6 steps
rust
use axum::{ extract::{FromRequestParts, Query}, http::{request::Parts, StatusCode}, };
Building a custom Axum extractor for query filters
extractors
query-parsing
enums
Intermediate
8 steps
python
import asyncio from dataclasses import dataclass import aiohttp
Bounded-concurrency HTTP fetching with asyncio
async
concurrency
semaphore
Intermediate
8 steps
rust
use std::sync::Arc; use axum::{ extract::{Multipart, State}, http::StatusCode,
Throttling file uploads in Axum with a Semaphore
concurrency
backpressure
multipart
Advanced
8 steps
rust
use once_cell::sync::Lazy; use regex::Regex; static NON_ALPHANUMERIC: Lazy<Regex> = Lazy::new(|| Regex::new(r"[^a-z0-9]+").unwrap());
Building URL slugs in Rust
string-processing
regex
transliteration
Intermediate
8 steps
go
package api import ( "errors"
Turning Gin validation errors into JSON
validation
error-handling
http-handlers
Intermediate
9 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/async-retry-with-exponential-backoff-in-rust-explained-rust-7791/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.