Passing immutable reference to async closure

Suppose I have a rather large struct, Value, and a function action: fn(Vec<&Value>) -> Vec<Value>. I want to run action asynchronously, using arguments stored in some struct like HashMap. So I write

// This is just a minimal example to show what I'm trying to do
async fn do_action(storage: HashMap<usize, Value>) {
    ...
    let arguments: Vec<&Value> = ...; // some Vec filled with immutable references to values in `storage`
    let handle = tokio::spawn(async move { action(arguments) });
    ...
    handle.await;
    ...
}

The async task is guaranteed to finish before the function returns or anything in storage drops. Now the compiler would complain that passing arguments to tokio::spawn would require a 'static reference while references to storage is not 'static.

How can I achieve the same effect without making large changes to the whole structure?

Assuming you have something like this:

async fn do_action() {
    let arguments: Vec<&Value> = Vec::new();
    let handle = tokio::spawn(async move { action(arguments) });
    other_stuff().await;
    handle.await;
}

You can use join.

async fn do_action() {
    let arguments: Vec<&Value> = Vec::new();
    let handle = action(arguments);
    tokio::join!(other_stuff(), handle);
}

Sorry I'm very new to async in Rust. I cannot understand why this code should work. The action is supposed to be synchronous and blocking (like Receiver::recv() or something like that). I need to do some other event listening while action is blocking. Won't this code block the thread at let handle = action(arguments)?

Ah okay, I missed that before. If it wasn't borrowing, you'd need to use spawn_blocking instead of spawn anyway. But since it is borrowing, you can use block_in_place. This has some drawbacks as stated in the docs, and you would need to spawn your async work before calling it if you want it to run in the background.

tokio::task::block_in_place(|| action(arguments));

Thank you for your suggestion, it seems to meet my needs. I will look furthur.

There's some other thoughts: if I make action an async function and use e.g. the async Receiver::recv().await, then the code won't block on action(arguments) and the problem could be solved, am I correct?

It won't block on action(arguments) but you'll still need to await it somewhere for it to run, most likely either inline or with join.

Thank you for your help! I'll try and investigate further.

1 Like