0

Noob in rust here, trying to implement the generic retry function and struggling how to resolve the lifetime error:

async fn retry<T, E, Fut, F>(mut f: F, client: &Client, max_retries: i32) -> Result<T, E>
    where
        Fut: Future<Output = Result<T, E>>,
        F: FnMut(&Client) -> Fut,
{
    let mut count = 0;
    loop {
        let result = f(client).await;

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

and the error is

| |_________________^ returning this value requires that `'1` must outlive `'2`

error[E0515]: cannot return value referencing temporary value

The way I am calling the method is:

let lambda = &mut move |client: &Client| {
            client
                .download_object(
                    &GetObjectRequest {
                        bucket: some_bucket,
                        object: some_object,
                        ..Default::default()
                    },
                    &Range::default(),
                )
        };
        retry(lambda, &self.client, 5).await

I am not able to understand the return value of f(client).await must outlive what exactly?

Full error:

error[E0621]: explicit lifetime required in the type of `client`
  --> src/main.rs:35:22
   |
28 | async fn retry<'a, T, E, Fut, F>(mut f: F, client: &Client, max_retries: i32) -> Result<T, E>
   |                                                    ------- help: add explicit lifetime `'a` to the type of `client`: `&'a Client`
...
35 |         let result = f(client).await;
   |                      ^^^^^^^^^ lifetime `'a` required

error: lifetime may not live long enough
  --> src/main.rs:68:13
   |
67 |           let lambda = &mut move |client: &Client| {
   |                                           -      - return type of closure `impl Future<Output = Result<Vec<u8>, google_cloud_storage::http::Error>>` contains a lifetime `'2`
   |                                           |
   |                                           let's call the lifetime of this reference `'1`
68 | /             client
69 | |                 .download_object(
70 | |                     &GetObjectRequest {
71 | |                         bucket: some_bucket,
...  |
75 | |                     &Range::default(),
76 | |                 )
   | |_________________^ returning this value requires that `'1` must outlive `'2`

error[E0515]: cannot return value referencing temporary value
  --> src/main.rs:68:13
   |
68 | /              client
69 | |                  .download_object(
70 | |                      &GetObjectRequest {
   | | ______________________-
71 | ||                         bucket: some_bucket,
72 | ||                         object: some_object,
73 | ||                         ..Default::default()
74 | ||                     },
   | ||_____________________- temporary value created here
75 | |                      &Range::default(),
76 | |                  )
   | |__________________^ returns a value referencing data owned by the current function

error[E0515]: cannot return value referencing temporary value
  --> src/main.rs:68:13
   |
68 | /             client
69 | |                 .download_object(
70 | |                     &GetObjectRequest {
71 | |                         bucket: some_bucket,
...  |
75 | |                     &Range::default(),
   | |                      ---------------- temporary value created here
76 | |                 )
   | |_________________^ returns a value referencing data owned by the current function

akram
  • 157
  • 1
  • 15

1 Answers1

1

I don't know what Client is so I can only guess, but my educated guess is that the future download_object() returns borrow from the client, but your retry() function signature does not allow that.

Since you're passing the Client to the function, the easiest way is to annotate the lifetime:

async fn retry<'a, T, E, Fut, F>(mut f: F, client: &'a Client, max_retries: i32) -> Result<T, E>
where
    Fut: Future<Output = Result<T, E>>,
    F: FnMut(&'a Client) -> Fut,
{
    // ...
}

If client was generated inside the function, it would be harder. See Calling a generic async function with a (mutably) borrowed argument.

Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77
  • thanks tried annotating with lifetime and the issue persist, The Client is the client from this `crate` - https://docs.rs/google-cloud-storage/latest/google_cloud_storage/client/struct.Client.html – akram Apr 20 '23 at 13:10
  • @akram From the error in the question it seems like you didn't use `&'a Client`. – Chayim Friedman Apr 20 '23 at 17:30
  • right, so `&'a Client` would solve one lifetime issue, and their is one more than remains: ``` &GetObjectRequest { | | ______________________- 71 | || bucket: some_bucket, 72 | || object: some_object, 73 | || ..Default::default() 74 | || }, | ||_____________________- temporary value created here ``` error[E0515]: cannot return value referencing temporary value – akram Apr 20 '23 at 18:29
  • my understanding for the second issue `cannot return value referencing temporary value` is, if the methods were not async then second error would not happen because it returns a Future, it's possible that the temporary values may go out of scope before the Future completes, resulting in a dangling reference and undefined behavior? any clues how to solve the second one too. – akram Apr 20 '23 at 18:35
  • @akram Now that's a different question, and also, I think it doesn't have a solution. – Chayim Friedman Apr 20 '23 at 18:43
  • thanks its clear, more dumb related question, if the retry method was not accepting client as an arg but only `closure` / `mut f: FnMut() -> Fut` in that case how the lifetime issue would be solved? – akram Apr 20 '23 at 19:05
  • @akram This is answered in the question I linked. – Chayim Friedman Apr 20 '23 at 19:09
  • I think its for scenario when client is generated inside function, i meant for scenario when client is not generated inside the function, here is an example to illustrate the same - https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=a9016feee306ac80588acaac8f66a5d1 – akram Apr 20 '23 at 19:15
  • @akram Well, if it's not created inside the function and not passed to it, where is it created? – Chayim Friedman Apr 20 '23 at 19:19
  • its created by the callee right before creating the closure. let client = Client:new(); let lambda = move || { client.download_object(&request, &range) }; – akram Apr 20 '23 at 19:28
  • @akram I don't believe this is solvable. You will have to use a custom trait, not a closure. – Chayim Friedman Apr 20 '23 at 19:35