Hello,
I'd like to have make a function that allows me to retry a function a certain number of times. I've taken a suggestion I saw on StackOverflow:
async fn retry<T, E, Fut, F>(retries: usize, mut f: F) -> Result<T, E>
where
F: FnMut() -> Fut,
Fut: Future<Output = Result<T, E>>,
{
let mut count = 0;
loop {
let result = f().await;
if result.is_ok() {
return result;
} else {
count += 1;
if count >= retries {
return result;
}
}
}
}
It works as long as the async block doesn't capture anything from the closure:
struct Foo {}
impl Foo {
async fn call(&self) -> Result<(), Infallible> {
unimplemented!()
}
}
async fn do_things() {
let mut foo = Foo {};
retry(1, || async { foo.call().await }).await.unwrap();
}
But it breaks when the async block captures variable from the closure, because of course the closure can now only be called once:
struct Foo {}
impl Foo {
async fn call_mut(&mut self) -> Result<(), Infallible> {
unimplemented!()
}
}
async fn do_things() {
let mut foo = Foo {};
retry(1, || async { foo.call_mut().await }).await.unwrap();
}
This leads to:
error: captured variable cannot escape `FnMut` closure body
--> src/lib.rs:43:17
|
38 | let mut foo = Foo {};
| ------- variable defined here
...
43 | retry(1, || async { foo.call_mut().await }).await.unwrap();
| - ^^^^^^^^---^^^^^^^^^^^^^^^^^^^
| | | |
| | | variable captured here
| | returns an `async` block that contains a reference to a captured variable, which then escapes the closure body
| inferred to be a `FnMut` closure
|
= note: `FnMut` closures only have access to their captured variables while they are executing...
= note: ...therefore, they cannot allow references to captured variables to escape
There is a workaround for that (see this rust-lang issue), which consists in wrapping Foo in a Rc<RefCell>, since the retry function will never be called before the future it returned during the previous iteration finished:
let rc_foo = Rc::new(RefCell::new(foo));
retry(1, || async { rc_foo.borrow_mut().call_mut().await })
.await
.unwrap();
But that's very un-ergonomic. Are there better solutions today on stable rust?