Why can tokio::main call !Send fns but tokio::spawn can not?

I am wondering why tokio::main can be part of a chain of .awaits where one async fn is !Send, but tokio::spawn can not?

Here is a playground where tokio::spawn isn't called, it compiles and runs even though one function is clearly !Send: Rust Playground

Here is a playground where tokio::spawn is called, which doesn't compile: Rust Playground

The only reason I can think of is that tokio::main should be thought of as "the main thread" of the program, analogous to the real main thread, and things that are called from that chain can't move between threads?

I've tried to find that definition in the tokio documentation but either I haven't looked hard enough, or it's just a case of "it's so obvious it doesn't have to be documented".

Does that make sense?

This is because #[tokio::main] uses block_on rather than tokio::spawn. When you use block_on, it does not require Send because a task running with block_on cannot moved across threads while it is running.

We would definitely appreciate a PR that elaborates on this in the documentation!

4 Likes