Skip to main content

relentless/
lib.rs

1//! `relentless` — a Rust library for retrying fallible operations and polling for conditions.
2//!
3//! This crate provides composable retry strategies with support for `std`, `alloc`,
4//! and `no_std` environments.
5//!
6//! # Custom wait strategies
7//!
8//! ```
9//! use core::time::Duration;
10//! use relentless::{RetryState, Wait, wait};
11//!
12//! struct CustomWait(Duration);
13//!
14//! impl Wait for CustomWait {
15//!     fn next_wait(&self, _state: &RetryState) -> Duration {
16//!         self.0
17//!     }
18//! }
19//!
20//! let strategy = CustomWait(Duration::from_millis(20))
21//!     .cap(Duration::from_millis(15))
22//!     .chain(wait::fixed(Duration::from_millis(50)), 2);
23//!
24//! let state = RetryState::new(3, None);
25//! assert_eq!(strategy.next_wait(&state), Duration::from_millis(50));
26//! ```
27//!
28//! # Extension-first usage
29//!
30//! In sync `std` builds, `.sleep(...)` is optional because `relentless` falls
31//! back to `std::thread::sleep`. The example below still calls `.sleep(...)`
32//! so it compiles under `no_std` documentation test runs too.
33//!
34//! ```
35//! use core::time::Duration;
36//! use relentless::{RetryExt, stop, wait};
37//!
38//! let result = (|| Err::<u32, &str>("transient"))
39//!     .retry()
40//!     .stop(stop::attempts(3))
41//!     .wait(wait::fixed(Duration::from_millis(5)))
42//!     .sleep(|_dur| {})
43//!     .call();
44//!
45//! assert!(result.is_err());
46//! ```
47
48#![no_std]
49#![forbid(unsafe_code)]
50
51// Compile-test README code examples as doctests.
52// Gated on `tokio-sleep` because the async example uses `sleep::tokio()`.
53#[cfg(all(doctest, feature = "tokio-sleep"))]
54#[doc = include_str!("../README.md")]
55mod readme_doctests {}
56
57#[cfg(feature = "alloc")]
58extern crate alloc;
59
60#[cfg(feature = "std")]
61extern crate std;
62
63mod compat;
64
65mod error;
66mod policy;
67pub mod predicate;
68pub mod sleep;
69mod state;
70mod stats;
71pub mod stop;
72pub mod wait;
73
74pub use error::{RetryError, RetryResult};
75pub use policy::RetryPolicy;
76pub use policy::{AsyncRetry, AsyncRetryExt, AsyncRetryWithStats};
77pub use policy::{
78    AsyncRetryBuilder, AsyncRetryBuilderWithStats, DefaultAsyncRetryBuilder,
79    DefaultAsyncRetryBuilderWithStats, DefaultSyncRetryBuilder, DefaultSyncRetryBuilderWithStats,
80    SyncRetryBuilder, SyncRetryBuilderWithStats,
81};
82pub use policy::{RetryExt, SyncRetry, SyncRetryWithStats};
83pub use predicate::Predicate;
84pub use sleep::Sleeper;
85pub use state::{AttemptState, ExitState, RetryState};
86pub use stats::{RetryStats, StopReason};
87pub use stop::Stop;
88pub use wait::Wait;
89
90/// Returns a [`SyncRetryBuilder`] with default policy: `attempts(3)`,
91/// `exponential(100ms)`, `any_error()`.
92///
93/// # Examples
94///
95/// ```
96/// use relentless::{retry, stop};
97///
98/// let result = retry(|_| Ok::<u32, &str>(42))
99///     .stop(stop::attempts(1))
100///     .sleep(|_| {})
101///     .call();
102/// assert_eq!(result.unwrap(), 42);
103/// ```
104pub fn retry<F, T, E>(
105    op: F,
106) -> SyncRetryBuilder<
107    stop::StopAfterAttempts,
108    wait::WaitExponential,
109    predicate::PredicateAnyError,
110    (),
111    (),
112    (),
113    F,
114    policy::NoSyncSleep,
115    T,
116    E,
117>
118where
119    F: FnMut(RetryState) -> Result<T, E>,
120{
121    SyncRetryBuilder::from_policy(RetryPolicy::new(), op)
122}
123
124/// Returns an [`AsyncRetryBuilder`] with default policy: `attempts(3)`,
125/// `exponential(100ms)`, `any_error()`.
126///
127/// # Examples
128///
129/// ```
130/// use core::time::Duration;
131/// use relentless::retry_async;
132///
133/// let retry = retry_async(|_| async { Ok::<u32, &str>(42) })
134///     .sleep(|_dur: Duration| async {});
135/// let _ = retry;
136/// ```
137pub fn retry_async<F, T, E, Fut>(
138    op: F,
139) -> AsyncRetryBuilder<
140    stop::StopAfterAttempts,
141    wait::WaitExponential,
142    predicate::PredicateAnyError,
143    (),
144    (),
145    (),
146    F,
147    Fut,
148    policy::NoAsyncSleep,
149    T,
150    E,
151>
152where
153    F: FnMut(RetryState) -> Fut,
154    Fut: core::future::Future<Output = Result<T, E>>,
155{
156    AsyncRetryBuilder::from_policy(RetryPolicy::new(), op)
157}