Hone logo
Hone
Problems

Robust Operation: Implementing Retry Logic in Rust

Network requests, external API calls, and file operations are inherently fallible. They can fail due to transient issues like network glitches, temporary service unavailability, or resource contention. Implementing robust retry logic is crucial for building resilient applications that can automatically recover from such temporary failures. This challenge will guide you through creating a generic retry mechanism in Rust.

Problem Description

You are tasked with creating a Rust function that can execute a given operation and automatically retry it a specified number of times if it fails. The operation itself will be a closure that returns a Result. If the operation succeeds (returns Ok), the function should return the successful value. If it fails (returns Err), the function should wait for a specified duration before retrying, up to a maximum number of attempts.

Key Requirements:

  • The retry function should be generic over the success type T and the error type E of the operation.
  • It should accept the operation (as a closure F: FnMut() -> Result<T, E>), the maximum number of retry attempts, and the delay between retries as parameters.
  • If the operation succeeds within the allowed attempts, return Ok(T).
  • If the operation fails after all retry attempts have been exhausted, return the last error encountered wrapped in an Err(E).
  • The delay between retries should be a std::time::Duration.

Expected Behavior:

The function should execute the provided operation. If it returns Ok, the result is immediately returned. If it returns Err, the function should pause for the specified duration and then attempt the operation again. This process repeats until either the operation succeeds or the maximum number of retries is reached.

Edge Cases to Consider:

  • An operation that succeeds on the first try.
  • An operation that fails multiple times but eventually succeeds.
  • An operation that consistently fails.
  • Zero retry attempts.

Examples

Example 1:

Input:
operation: `|| { static mut count: i32 = 0; unsafe { count += 1; if count < 3 { Err("Temporary error") } else { Ok("Success!") } } }`
max_attempts: 5
delay: 100ms

Output:
Ok("Success!")

Explanation: The operation fails the first two times, triggering retries. On the third attempt, it succeeds and returns "Success!".

Example 2:

Input:
operation: `|| Err("Persistent error")`
max_attempts: 3
delay: 50ms

Output:
Err("Persistent error")

Explanation: The operation fails on the first attempt. It is retried twice more, failing each time. After the third failed attempt, the last error ("Persistent error") is returned.

Example 3:

Input:
operation: `|| Ok(100)`
max_attempts: 10
delay: 1s

Output:
Ok(100)

Explanation: The operation succeeds on the very first attempt. No retries are performed.

Constraints

  • max_attempts will be a usize and will be greater than or equal to 0.
  • delay will be a std::time::Duration and will be a positive duration.
  • The closure F will return a Result<T, E>.
  • The error type E must implement Debug to be printable if needed for debugging purposes.

Notes

  • You will need to use std::thread::sleep for introducing the delay.
  • Consider how to handle the max_attempts parameter. If max_attempts is 0, the operation should only be attempted once.
  • The function signature should look something like: fn retry<T, E, F>(operation: &mut F, max_attempts: usize, delay: std::time::Duration) -> Result<T, E> where F: FnMut() -> Result<T, E>, E: std::fmt::Debug.
  • The use of FnMut for the closure allows it to mutate its environment if necessary (as seen in Example 1).
Loading editor...
rust