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

Ah, we had this sort of issue only recently on the forum. It’s actually related to the Send bound. A terribly annoying compiler bug, not only because it’s unnecessarily struggling to deduce the Send here, but more-so because the error message gives zero indication about it.

Here you can see how the issue comes and goes with a T: Send:

#[tokio::main]
async fn main() {
    // This works fine
    let mut foo = MyWriter { writer: vec![] };
    foo.write(SOME_FOO).await.unwrap();

    send(async move {
        let mut foo = MyWriter { writer: vec![] };
        foo.write(SOME_FOO).await.unwrap();
    });
}

fn send<T>(x: T)
where
    T: Send, // comment out this line and it compiles ...
{
}

One way to fix these quite reliably seems to be to apply some type erasure, and box the right thing, in this case boxing the right Future seems to work well [in the other thread, it was a Stream that could be boxed]:

+ use futures::FutureExt;
…

    tokio::spawn(async move {
        let mut foo = MyWriter { writer: vec![] };
-       foo.write(SOME_FOO).await.unwrap();
+       foo.write(SOME_FOO).boxed().await.unwrap();
    })
    .await
    .unwrap();

If you aren’t using the futures crate (or at least the futures-util part of it), you can of course also do this without the convenience .boxed() method,

    tokio::spawn(async move {
        let mut foo = MyWriter { writer: vec![] };
        (Box::pin(foo.write(SOME_FOO))
            as std::pin::Pin<Box<dyn Future<Output = io::Result<_>> + Send>>)
            .await
            .unwrap();
    })
    .await
    .unwrap();

but the simplicity of .boxed() will of course make it easier - especially when you need to experiment a bit until you find the best place(s) to add it.

3 Likes