0

I have two fallible async functions I want to execute in parallel. The problem is that both functions take a reference, and the parameters to my wrapper function are also references. This gives me an error when I try to use task::spawn because the reference escapes the function. I know that a borrow may not outlive a function body, but as you can see, I am actually joining these tasks, so the references would not outlive the body of this function.

async fn decode_tokens<'auth, 'id>(
    x_authorization: &'auth str,
    x_user: &'id str,
) -> Result<AuthorizedUser, AuthorizationError> {
    let decode_auth_handle = task::spawn(decode_auth_token(x_authorization)); // error[E0521]: borrowed data escapes outside of function
    let decode_id_handle = task::spawn(decode_id_token(x_user)); // error[E0521]: borrowed data escapes outside of function

    // handles are joined here
    let auth_token = decode_auth_handle.await??; 
    let id_token = decode_id_handle.await??;

    Ok(AuthorizedUser {
        id_token: id_token,
        authorization_token: auth_token,
    })
}

Is my understanding wrong, or did I get my lifetime annotations wrong?

Also, I'd like to note that the functions decode_auth_token and decode_id_token both return Owned Results

I think I'd be able to get around this by annotating the return type of this function, but since the return type is also an Owned result, I don't think I can annotate it

Brandon Piña
  • 614
  • 1
  • 6
  • 20
  • 1
    Please post a minimal reproducible example, and the full errors from `cargo check`. – Chayim Friedman Mar 01 '23 at 21:32
  • @ChayimFriedman Like I said, I am spawning a task that takes a borrowed value and giving it a parameter from the enclosing function which also is a borrowed value. Considering that I join the spawned tasks, I would assume that the lifetime would still be valid for the borrowed values I passed for the lifetimes because the borrow doesn't *technically* outlive this function – Brandon Piña Mar 01 '23 at 21:40
  • 1
    What this is an explanation, while I asked for a MRE. The most minimal complete snippet of code that we can run and see the error. https://stackoverflow.com/help/minimal-reproducible-example. – Chayim Friedman Mar 01 '23 at 21:42
  • This looks like a work for something like [`tokio-scoped`](https://crates.io/crates/tokio-scoped). – rodrigo Mar 01 '23 at 22:09
  • @rodrigo I'm using a re-export of Tokio from a web framework. I assume that Tokio-scoped would work with it? – Brandon Piña Mar 01 '23 at 22:12
  • @BrandonPiña TBH I don't know and I confess I never used `tokio-scoped` myself. But I don't see why not, the Cargo dependency solver is great doing this kind of things, as long as your dependencies have compatible versions, of course. But there is only one way to say for sure... – rodrigo Mar 01 '23 at 22:28
  • 1
    Do you really need parallelism as opposed to simple concurrency? I.e. would [`join`](https://docs.rs/tokio/latest/tokio/macro.join.html) work for your use case: `let (auth_token, id_token) = join!(decode_auth_token (x_authorization), decode_id_token (x_user));` – Jmb Mar 01 '23 at 22:28
  • @Jmb depends on if I get [E0521] using join. I can give it a shot – Brandon Piña Mar 01 '23 at 23:34
  • @Jmb It doesn't give me any compilation errors, so I'm going to tentatively say that did the trick. My frontend is giving me issues at the moment – Brandon Piña Mar 01 '23 at 23:46

1 Answers1

1

Event though it returns an awaitable handle, spawn is intended more for long-running operations in fire and forget mode, or if you want to run more operations in the current function while the spawned task is running in the background. If you simply want to start multiple futures and await them all concurently, it's usually better (and certainly easier) to use join:

let (auth_token, id_token) = join!(
   decode_auth_token (x_authorization),
   decode_id_token (x_user));

Ok(AuthorizedUser {
    id_token: id_token?,
    authorization_token: auth_token?,
})
Jmb
  • 18,893
  • 2
  • 28
  • 55