Future cannot be sent between threads safely, but only with generic impl

Hi,

I was hacking on my personal project trying to generalize a few things and I stumbled on an error that I don't quite understand. I have a following trait:

trait Service: Send + Sync {
    type Response: Send + Serialize;
    type Request: DeserializeOwned + Send;

    fn handle_request(
        &self,
        message: Self::Request,
    ) -> Pin<Box<dyn Future<Output = Self::Response> + Send + '_>>;
}

When I implement the trait on a concrete type everything is fine, here is an example:

impl Service for WorldServer
{
    type Response = WorldResponse;
    type Request = WorldRequest;

    fn handle_request(
        &self,
        message: Self::Request,
    ) -> Pin<Box<dyn Future<Output = Self::Response> + Send + '_>> {
        Box::pin(async {
            match message {
                WorldRequest::HelloRequest(hello_request) => WorldResponse::HelloResponse(self.hello(hello_request.name).await),
            }
        })
    }
}

This is basically matching the message and calling a concrete method on the WorldServer type. At the moment it only handles one function, but you can imaging more functions being handled in the match statement.

Now I wanted to change the implementation so the Service is implemented for any type that implements a certain trait, for example:

impl<T> Service for T
where
    T: World + Sync + Send,
{
    type Response = WorldResponse;
    type Request = WorldRequest;

    fn handle_request(
        &self,
        message: Self::Request,
    ) -> Pin<Box<dyn Future<Output = Self::Response> + Send + '_>> {
        Box::pin(async {
            match message {
                WorldRequest::HelloRequest(hello_request) => WorldResponse::HelloResponse(self.hello(hello_request.name).await),
            }
        })
    }
}

Here I implement for any type T, which implements the World trait (which is also a trait that WorldServer had implemented in the previous example). The problem is that now I get the following error:

error: future cannot be sent between threads safely
   --> utils/src/main.rs:123:9
    |
123 | / ...   Box::pin(async {
124 | | ...       match message {
125 | | ...           WorldRequest::HelloRequest(hello_request) => WorldResponse::HelloResponse(self.he...
126 | | ...       }
127 | | ...   })
    | |________^ future created by async block is not `Send`
    |
    = help: within `[async block@utils/src/main.rs:123:18: 127:10]`, the trait `std::marker::Send` is not implemented for `impl futures::Future<Output = std::string::String>`
note: future is not `Send` as it awaits another future which is not `Send`
   --> utils/src/main.rs:125:91
    |
125 | ...dResponse::HelloResponse(self.hello(hello_request.name).await),
    |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ await occurs here on type `impl futures::Future<Output = std::string::String>`, which is not `Send`
    = note: required for the cast from `Pin<Box<[async block@utils/src/main.rs:123:18: 127:10]>>` to `Pin<Box<dyn futures::Future<Output = WorldResponse> + std::marker::Send>>`

I'm guessing that because the trait has different bounds than the concrete type, I'm missing some bounds in the definition, but I fail to see what would help here. Any ideas on what's going on here?

Looks like your question is missing some relevant context. The error message points to the future returned by self.hello(hello_request.name), and I would have to guess this .hello method comes from the T: World (though I’m not sure why it’s an impl Future<Output = String> type then, if it comes from a trait method). But you don’t show us the definition of hello / World.

Can you create a reproducible snippet in the playground? As Stephan said, it's necessary to see the whole picture to know what's going on.

Sorry, I forgot about the trait :sweat_smile:. I'm using async_fn_in_trait, so it's as simple as:

trait World {
    async fn hello(&self, name: String) -> String;
}

For completness I created a playground snippet: Rust Playground

For context: I'm writing a simple RPC system. I plan to generate most of the boilerplate (like requests, responses, Service impl etc) in a macro, but first I want to have solid Rust code to generate. I want Service to be generic, cause otherwise it needs the concrete type to already have implementation of a trait it should handle, which makes it harder to use the whole system. For example imagine someone creating the World service as in the example:

#[service]
trait World {
    async fn hello(&self, name: String) -> String;
}

The service macro would generate all of the boilerplate, but if I generate impl Service for WorldService I have to already provide the concrete implementation for this interface, which is not usually desired. You would rather define just the interface in a shared module and only define the concrete implementation on the server side.

Another problem I was facing is that Rust is not happy with implementing Service for multiple types, like even if I fix the cannot be sent between threads I will get conflicting implementations, but I figured I'll cross that bridge when I get there (although if anyone wants to take a look here is the error: Rust Playground)

You'll have to define the trait using

fn (...) -> impl Future<Output = ... > + Send

style syntax. Even if defined like this in the trait definition, the implementations can still use async fn syntax, as far as I know.

This is still a major usability concern for this unstable feature and as far as I recall from discussions I've read on Github, it will probably have to be addressed somehow - at the very least by something like a lint that would help you figure out this syntax change I described above, yourself.

Looks like for your for a concrete type, it worked fine, because the auto traits implemented by an async fn closure would be made available, inferred from the implemention, so the Send-ness is known for the specific implementation of Hello for WorldServer, even if it wasn't required by the trait in general, and thus wasn't available in the generic version.

2 Likes

A neat, a playground. Then I can even demonstrate the fix from my phone: Rust Playground

Oooh, now that makes sense. Now I'm wondering, is there any way to declare the async fn output as Send without the explicit Future syntax? I guess I'll have to do more research into that.

Thanks for help!

As far as I'm aware: No, at least not currently. But as mentioned, at least this only affects trait definitions, the impl can use async fn syntax. And if it stays this way when the feature lands, there'll probably be proc-macros that would add Send bounds to all trait methods in a trait definition - at least that sounds like a convenient and simple macro to me.

3 Likes

BTW: There's also an incomplete feature which could make this more convenvient in the future.

See here. (line 55)

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.