Search code examples
rustrust-tonic

Unable to acquire the right form of ownership to repository


I'm trying to write a small webapp communicating with a client (aka Slave in this code) over gRPC (using Tonic) and I'm getting stuck almost at the very beginning. I want the slave to be able to self-register by invoking the register() function over gRPC. The request at the moment contains only the mac_address (due to lack of any better unique device identifier I'm aware of).

The juicy part of the code looks as follows

pub struct SlaveServer {
    slave_repo: Arc<SlaveRepository>
}

#[tonic::async_trait]
impl SlaveManager for SlaveServer {
    async fn register(&self, request : Request<RegistrationRequest>) -> Result<Response<RegistrationResponse>, Status> {
        let req: RegistrationRequest = request.into_inner();

        (*self.slave_repo).add_slave(
            Slave {
                mac_address: req.mac_address.clone()
            }
        );

        println!("New slave: {}", req.mac_address);
        let response = RegistrationResponse { slave_id: "new_id".to_string() };
        return Ok(Response::new(response))
    }
}

unfortunately however it looks like I cannot invoke add_server. It currently gives me the following error:

trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Arc<SlaveRepository>`

It is my guess that an easy way to go around it would be to write register(&mut self,...), but that is not something I can even attempt, given that the SlaveManager is auto-generated from a proto file and that one doesn't use the mut.

I originally used Box<SlaveRepository>, but that didn't seem to work either, plus I suppose it wasn't suitable since the SlaveRepository will be used in several different places.


Solution

  • The Arc<T> smart pointer gives you read-only access to its pointee. So you cannot modify something which is behind an Arc, at least not directly.

    Given that you are using Arc which is a thread-safe reference counted pointer, I assume that this would be used across threads. In that case you should wrap your SlaveRepository in some synchronization primitive, such as Mutex<T> or RWLock<T> (note that in async context you should use the async versions of those primitives, instead of the ones from std):

    pub struct SlaveServer {
        slave_repo: Arc<RWLock<SlaveRepository>>
    }
    
            // skipped for brevity
            let mut lock_guard = self.slave_repo
                .write()
                .await;
    
            lock_quard.add_slave(
                Slave {
                    mac_address: req.mac_address.clone()
                }
            );
    

    This is called the interior mutability pattern