Search code examples
rustasync-await

How to write a retry function in Rust that involves async


Let's say I have a function like below that could fail. The function is also async

async fn can_fail() -> Result<i32, Box<dyn std::error::Error>> {
    let mut rng = rand::thread_rng();
    let random: u8 = rng.gen();
    if random % 2u8 == 0 {
        Ok(42)
    } else {
       Err("error".to_string().into())
    }
}

Now I will like to implement a retry function that can be used to retry a function like can_fail.

I came up with this in my attempt

fn retry<F: Fn() -> Result<i32, Box<dyn std::error::Error>>>(f: F, retries: i32) -> Result<i32, Box<dyn std::error::Error>>
    {
    let mut count = 0;
    loop {
        let result = f();

        if result.is_ok() {
            break result;
        } else {
            if count > retries {
             break result
            }
            count += 1;
        }
    }
}

Then in my attempt to use, I tried to put can_fail into a closure like this

    let my_closure: Box<dyn Fn() -> Result<i32, Box<dyn std::error::Error>>> = Box::new(|| {
        can_fail().await
    });

But this fails with the error

error[E0728]: `await` is only allowed inside `async` functions and blocks
   --> src/main.rs:208:19
    |
207 |     let my_closure: Box<dyn Fn() -> Result<i32, Box<dyn std::error::Error>>> = Box::new(|| {
    |                                                                                         -- this is not `async`
208 |         can_fail().await
    |                   ^^^^^^ only allowed inside `async` functions and blocks

So I am kinda stuck. So my question are:

  1. Is the retry I came up with do the job? I cannot tell as I cannot even pass in a closure to it
  2. How do I fix the await is only allowed inside async functions and blocks` error in this scenario?
  3. Also is it possible to make retry maybe more generic? To get started I hard coded the return type of the function to be returned. In real life I would want that to be generic. How can that be achieved?

Solution

  • Your retry() function looks OK, but to pass an async function into it you need to fix it to accept a function returning Future, and to be able to .await it, make it async:

    async fn retry_async<Fut, F: Fn() -> Fut>(f: F, retries: i32) -> Result<i32, Box<dyn std::error::Error>>
    where
        Fut: Future<Output = Result<i32, Box<dyn std::error::Error>>>,
    {
        let mut count = 0;
        loop {
            let result = f().await;
    
            if result.is_ok() {
                break result;
            } else {
                if count > retries {
                    break result;
                }
                count += 1;
            }
        }
    }
    
    retry(can_fail, 3).await.expect("failed to many times");
    

    You can also make the function more generic by:

    • Taking FnMut instead of Fn.
    • Allowing a generic return type.
    • Allowing a generic error type.
    async fn retry_async<T, E, Fut, F: FnMut() -> Fut>(mut f: F, retries: i32) -> Result<T, E>
    where
        Fut: Future<Output = Result<T, E>>,
    {
        let mut count = 0;
        loop {
            let result = f().await;
    
            if result.is_ok() {
                break result;
            } else {
                if count > retries {
                    break result;
                }
                count += 1;
            }
        }
    }