M3rtzi
November 21, 2022, 6:06am
1
I'm trying to write an async method with a retry logic like this:
async fn find_or_refetch<'a>(
&'a mut self,
kid: &'a str,
i: i32,
) -> BoxFuture<'a, Result<(), ()>> {
async move {
if let Some(jwk) = self.jwks.find(kid) {
Ok(())
} else if i < 2 {
self.find_or_refetch(kid, i + 1).await.await
} else {
Err(())
}
}
.boxed()
}
But it's causing a hard to understand (and really long) compiler error, here's a piece of it:
error[E0391]: cycle detected when computing type of `auth::<impl at sbox_http/src/auth.rs:41:1: 41:15>::find_or_refetch::{opaque#0}`
--> sbox_http/src/auth.rs:66:10
|
66 | ) -> BoxFuture<'a, Result<(), ()>> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
note: ...which requires borrow-checking `auth::<impl at sbox_http/src/auth.rs:41:1: 41:15>::find_or_refetch`...
--> sbox_http/src/auth.rs:62:5
|
62 | / async fn find_or_refetch<'a>(
63 | | &'a mut self,
64 | | kid: &'a str,
65 | | i: i32,
66 | | ) -> BoxFuture<'a, Result<(), ()>> {
| |______________________________________^
note: ...which requires processing `auth::<impl at sbox_http/src/auth.rs:41:1: 41:15>::find_or_refetch`...
--> sbox_http/src/auth.rs:62:5
There are many more notes:
which requires unsaftey-checking
which requires building MIR...
which requires borrow checking...
which requires processing...
which requires building THIR...
which requires type-checking...
etc.
Very grateful for any pointers as to what i need to do to make this work? Or is there a better way to write an async method with retry/backoff logic in rust?
You're getting a cycle because async fn() -> T
is really just syntax sugar for fn() -> impl Future<Output = T>
. In your case, that means borrow checking your function needs to know if your function passes the borrow checker, which is a cycle. Returning a future from an async function is almost always the wrong thing, just convert it to an ordinary function returning a boxed future instead.
2 Likes
Hyeonu
November 21, 2022, 6:15am
3
Or you can make it regular async fn, and modify the recursive calling code like this:
self.find_or_refetch(...).boxed().await
1 Like
M3rtzi
November 21, 2022, 6:28am
4
@Aiden2207 & @Hyeonu - Thank you for the swift reply, that indeed solved it!
This now seems to check out with the compiler - seems about right to you?
fn find_or_refetch<'a>(&'a mut self, kid: &'a str, i: i32) -> BoxFuture<Result<&'a Jwk, ()>> {
if let Some(jwk) = self.jwks.find(kid) {
async move { Ok(jwk) }.boxed()
} else if i < 2 {
self.find_or_refetch(kid, i + 1)
} else {
async move { Err(()) }.boxed()
}
}
M3rtzi
November 21, 2022, 7:17am
5
ah I was a bit too quick to think it all wokred I need to await another async function within this recursive method. So this is really what I need:
async fn find_or_refetch<'a>(&'a mut self, kid: &'a str, i: i32) -> Result<Jwk, ()> {
async move {
if let Some(jwk) = self.jwks.find(kid).cloned() {
Ok(jwk)
} else if (i < 2) {
self.refresh().await;
self.find_or_refetch(kid, i + 1).await
} else {
Err(())
}
}
.boxed()
.await
}
but now I'm back at the first error...
H2CO3
November 21, 2022, 7:25am
6
The whole point of the suggestion was to not .await
the result of the recursive call directly, since that's exactly what is causing the problem. You'd rewrite this as
async fn find_or_refetch<'a>(&'a mut self, kid: &'a str, i: i32) -> Result<Jwk, ()> {
if let Some(jwk) = self.jwks.find(kid).cloned() {
Ok(jwk)
} else if (i < 2) {
self.refresh().await;
self.find_or_refetch(kid, i + 1).boxed().await
} else {
Err(())
}
}
M3rtzi
November 21, 2022, 7:34am
7
@H2CO3 I'm confused that just seems to cause the exact same compiler error cycle detected...
. I'm using rust rustc 1.65.0 (897e37553 2022-11-02)
.
Will try to setup a playground to reproduce the error.
You're not expected to use both async fn
and async
block. You're expected to use either the async fn
with raw code inside it (like H2CO3 suggested), or the ordinary fn
returning BoxFuture
(that might be easier, since there's no anonymous types).
2 Likes
H2CO3
November 21, 2022, 7:42am
9
Hmm, indeed. Rewriting as a regular function returning BoxFuture
and applying the then
combinator instead of .await
works though: Playground .
1 Like
M3rtzi
November 21, 2022, 7:46am
10
This finally seems to both compile and do what I need:
fn find_or_refetch<'a>(&'a mut self, kid: &'a str, i: i32) -> BoxFuture<Result<Jwk, ()>> {
async move {
if let Some(jwk) = self.jwks.find(kid).cloned() {
Ok(jwk)
} else if (i < 2) {
self.refresh().await;
self.find_or_refetch(kid, i + 1).await
} else {
Err(())
}
}
.boxed()
}
I indeed got it wrong in defining the function as async as @Cerber-Ursi pointed out.
Thank you all for the great input I think I almost got it now
system
Closed
February 19, 2023, 7:46am
11
This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.