Why tokio::join! doesn't require 'static lifetime of the passed-in futures?

As the subject says, I don't fully understand why something like this work

async task1(arg: &String) {...}
async task2(arg: &String) {...}

#[tokio::main]
async fn main()
{
let data = "hello".to_owned();
tokio::join!(task1(&data), task2(&data));
...
}

But this doesn't

#[tokio::main]
async fn main()
{
let data = "hello".to_owned();
tokio::join!(tokio::spawn(task1(&data)), tokio::spawn(task2(&data)));
...
}

I kind of understand why tokio::spawn requires for the passed-in futures to be 'static, but I can't fit in my (possibly poor) mental model why that's not the case when passing the futures directly to tokio::join! as in the first example.
Maybe it's related to the fact that without spawn the two futures are driven to completion on the same thread the calling function is executing?
But even if that's the case I'd like for someone to elaborate a bit on the reasoning to use here...

Thanks

In the non-spawn case, both futures are run on the current task. Remember that 'tokio::main' macro actually creates a task with the body of your main function and then blocks on it. This means that any data in the task (a string in this case) lives for the lifetime of the joined tasks (since it async blocks until they are both completed).

In the spawn case, you'd actually be OK as well (I think) because your task is at the top level for the whole tokio instance and the instance ends when the task completes, but the compiler doesn't know this and so can't accept the program that uses task::spawn, because in general the current task could finish while the spawned tasks were still running.

Many thanks, makes sense.

1 Like

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.