Implementation of trait is not general enough when used inside tokio::spawn

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 :frowning:


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 :wink:

3 Likes