Hi there,
I was trying to implement an async function retry_on_failure
that receives a closure future_callback
which returns a future. The main purpose of the retry_on_failure
is to catch the result returned from the future_callback
and if it is an Err
variant and the error is known, perform some operation that will prevent the error happening second time and recall the future_callback
again. However, I am having some problem with lifetimes since both retry_on_failure
and future_callback
requires a reference to self
.
Here is a playground link that shows the problem with minimal effort. And the code is right below:
use std::future::Future;
struct Client {
id: i32,
token: String
}
impl Client {
async fn retry_on_fail<'a, 'b, T, R, L>(
&'a mut self,
future_callback: &(dyn Sync + Fn(&'b Self) -> R),
login: &(dyn Sync + Fn(i32) -> L)
) -> Result<T, &'static str>
where
'a: 'b,
R: Future<Output = Result<T, &'static str>>,
L: Future<Output = Result<String, &'static str>>,
{
let token = match future_callback(self).await {
Err("retry") => {
match login(self.id).await {
Ok(token) => token,
Err(e) => return Err(e),
}
},
res => return res,
};
self.token = token;
future_callback(self).await
}
async fn fetch_logs(&self) -> Result<Vec<()>, &'static str> {
Err("retry")
}
}
async fn login(id: i32) -> Result<String, &'static str> {
Ok(format!("New token for {id}"))
}
pub fn main() {
async {
let _ = Client { id: 1, token: "Old token".to_string() }
.retry_on_fail(&|client| client.fetch_logs(), &login)
.await;
};
}
The error says that:
Compiling playground v0.0.1 (/playground)
error[E0506]: cannot assign to `self.token` because it is borrowed
--> src/main.rs:29:9
|
19 | let token = match future_callback(self).await {
| ---- `self.token` is borrowed here
...
29 | self.token = token;
| ^^^^^^^^^^ `self.token` is assigned to here but it was already borrowed
30 |
31 | future_callback(self).await
| --------------- borrow later used here
For more information about this error, try `rustc --explain E0506`.
error: could not compile `playground` (bin "playground") due to previous error
When I look the error, I feel like the borrow at line 19 should end before the line 29 since the future constructed in line 19 is also moved and dropped at there. Hence, the mutable borrow at line 29 should not cause a problem for the borrow at line 31.
I have multiple feelings about the error. One is that it is not possible to implement retry_on_failure
without passing ownership of the self
to the future_callback
. Second is that I am missing some lifetime bounds on the retry_on_failure
method.
Thirdly, it might be a limitation of the async rust or even a bug. I found this issue on the github which may be related to this one.
I have found a solution that overcomes the lifetime errors in this playground, however, it requires an allocation if Client
has additional fields like another String
.
Can anyone explain the reasons behind this error or point me out to a solution?
Thanks in advance.