Search code examples
rustdependency-injectioninversion-of-controlrust-tonic

`Send` is not implemented when attempting Inversion of Control in Rust


Summary

When trying to have a tonic server class depend on a trait, I get a compile-time issue about the trait not implementing Send or Sync. My intent is to have the server class depend on the trait rather than the concrete implementation of that trait (inversion of control).

Setup

I have a RegistrationServiceApi trait that is implemented by a RegistrationServiceImpl

pub trait RegistrationServiceApi {
    fn register_user(&self, request: RegisterUserRequest) -> RegisterUserResponse;
}

#[derive(Debug, Default)]
pub struct RegistrationServiceImpl {}

impl RegistrationServiceApi for RegistrationServiceImpl {
    fn register_user(&self, request: RegisterUserRequest) -> RegisterUserResponse {
        ...
    }
}

I also have a RegistrationServiceServerImpl that is implementing a RegistrationService gRPC class generated by prost (annotated with #[tonic::async_trait]) that depends on theRegistrationServiceApi.

pub struct RegistrationServiceServerImpl {
    registration_service: Arc<dyn RegistrationServiceApi>,
}

impl RegistrationServiceServerImpl {
    pub fn new(registration_service: Arc<RegistrationServiceImpl>) -> Self {
        RegistrationServiceServerImpl {
            registration_service,
        }
    }
}

#[tonic::async_trait]
impl RegistrationService for RegistrationServiceServerImpl {
    async fn register_user(
        &self,
        request: Request<RegisterUserRequest>,
    ) -> Result<Response<RegisterUserResponse>, Status> {
        let req = request.into_inner();
        Ok(Response::new(self.registration_service.register_user(req)))
    }
}

Problem

The setup above yields the following compiler error:

error[E0277]: `(dyn RegistrationServiceApi + 'static)` cannot be sent between threads safely
   --> project/src/registration_service_server.rs:21:6
    |
21  | impl RegistrationService for RegistrationServiceServerImpl {
    |      ^^^^^^^^^^^^^^^^^^^ `(dyn RegistrationServiceApi + 'static)` cannot be sent between threads safely
    |
    = help: the trait `Send` is not implemented for `(dyn RegistrationServiceApi + 'static)`
    = note: required because of the requirements on the impl of `Sync` for `Arc<(dyn RegistrationServiceApi + 'static)>`
note: required because it appears within the type `RegistrationServiceServerImpl`
   --> project/src/registration_service_server.rs:8:12
    |
8   | pub struct RegistrationServiceServerImpl {
    |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: required by a bound in `RegistrationService`
   --> 
    |
194 |     pub trait RegistrationService: Send + Sync + 'static {
    |                                           ^^^^ required by this bound in `RegistrationService`

Attempts

I've tried replacing the dependency on the trait with a dependency on the implementation class:

pub struct RegistrationServiceServerImpl {
    registration_service: Arc<RegistrationServiceImpl>,
}

This works but violates inversion of control.

I've also tried having the RegistrationServiceApi extend Send and Sync, but that doesn't resolve the issue. In hindsight, that makes sense given the error was about dyn RegistrationServiceApi + 'static.


Solution

  • The solution, thanks to Chayim Friedman was to add both Sync and Send as supertraits.

    pub trait RegistrationServiceApi: Send + Sync {
        fn register_user(&self, request: RegisterUserRequest) -> RegisterUserResponse;
    }
    
    pub struct RegistrationServiceServerImpl {
        registration_service: Arc<dyn RegistrationServiceApi>,
    }