Yes, I have actually found a nice solution now.
Trying out a few things, I think I’ve found out that the only actually relevant property of these boxed, type-erased futures/streams – which made them an effective workaround for this compiler bug & error – is that they implement Send
unconditionally.
So turns out: a wrapper type struct AlwaysSend<Fut>
with an unconditional impl<Fut> Send for AlwaysSend<Fut>
(not restricted to Fut: Send
) can offer the same effect, even when Fut
is still the original future type and some poll
method directly delegates to it; without introducing any boxing, or type erasure with dynamic calls; so there should be no overhead at all.
Of course, Send
is relevant for soundness
But wait!! it suffices to enforce the Fut: Send
thing when constructing the wrapper, so there we have a way to encapsulate this workaround wrapper type in a safe API.
[This mirrors the property of dyn Future<…> + Send
being always Send
itself, but when you construct it, you’ll need to prove the thing you’ve actually put in is Send
.]
For simplicity, I’ve just uploaded such a type to crates.io now. The crate even offers a similarly convenient API through its own extension traits FutureExt
and StreamExt
. So in places where adding a call to .boxed()
might help as I’ve explained above, instead a call to .always_send()
in the right place(s) should solve your issue equally well