For what it's worth: tokio::join!
and futures::join!
are unsound, but std::future::join!
is not. tokio::join!
moves the input futures into a lambda, similar to the original example, and so does futures::join!
(whose implementation is more complicated). In contrast, std::future::join
pins the input futures outside the lambda and only moves Pin<&mut T>
objects into the lambda.
Here is a playground link, pieced together from some of the previous snippets, that triggers a Miri error using tokio::join!
and no unsafe
. If you change tokio::join!
to futures::join!
it also triggers an error.
And... here is a playground link that actually triggers a miscompilation using futures::join!
. The code should print 44, but in release mode it prints 43. This doesn't work with tokio::join!
simply because it generates more complicated control flow that confuses LLVM.