rustconcurrencyactix-web

Trying to pass in memory data store around


I'm hacking at an Actix API server, and I need to track objects in memory in a hashmap and pass them around between different threads. To do that, I'm passing in a Storage object to the app_data of the server:

    let storage = local_storage::Storage::load(&cli.root_dir)?;
    info!("Storage: {:?}", storage);

    HttpServer::new(move || {
        App::new()
            .app_data(web::Data::new(storage.clone()))
            .service(web::scope("/stores")
                     .service(get_stores)
                     .service(create_store)
                     .service(describe_store)
                     .service(delete_store))
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await

I want to keep this local Storage object to handle all interfacing with the file system and track the existing stores:

#[derive(Debug, Clone)]
pub struct Storage {
    stores: Arc<RwLock<HashMap<String, Store>>>,
    root_dir: String,
}

impl Storage {
    // Load all directories from root directory into the store on startup
    pub fn load(root_dir: &String) -> io::Result<Self> {
        let mut stores = HashMap::new();
    
        // Does some stuff
    
        Ok(Self { stores: Arc::new(RwLock::new(stores)), root_dir: root_dir.to_string() })
    }

    // Create a new directory in the root directory and add it to the in memory store
    pub fn add(&mut self, id: String) -> io::Result<()> {
        fs::create_dir(format!("{}/{}", self.root_dir, id))?;

        self.stores.write().unwrap().insert(id.clone(), Store::new(id.clone()));

        Ok(())
    }
}

Then, I try to add a new store from API itself:

#[post("")]
async fn create_store(store: web::Json<Store>, data: web::Data<local_storage::Storage>) -> Result<(), error::Error> {
    match data.add(store.id.clone()) {
        Ok(_) => Ok(()),
        Err(e) => Err(error::ErrorServiceUnavailable(e)),
    }
}

This is the error I get:

error[E0596]: cannot borrow data in an `Arc` as mutable
  --> src/main.rs:21:11
   |
21 |     match data.add(store.id.clone()) {
   |           ^^^^ cannot borrow as mutable
   |
   = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Arc<Storage>`

For more information about this error, try `rustc --explain E0596`.

I'm trying to detangle this in a way that Rust accepts and is idiomatic. I've been tweaking a bunch of things, but can't seem to make the compiler happy. Any suggestions on how I should restructure this in a way that will work?


Solution

  • Note that in your example, the add method doesn't necessarily require &mut self. This might sound counterintuitive, because &mut self is typically needed when you modify the inner data of your structure—and this is exactly what you're doing here! However, you can use structures that provide so-called "interior mutability", which allow you to modify the data with borrow checks happening at runtime, rather than compile time. Thanks to this, you can modify data passed by &self, rather than &mut self. One such structure is RwLock, which you are already using in your example.

    So, the solution is just to drop mut from your add method:

    impl Storage {
        pub fn add(&self, id: String) -> io::Result<()> {
            // ...
        }
    }
    

    Compiler should no longer complain, as you won't be trying to borrow data in Arc as mutable. As an extra note, this error could have also been detected automatically by clippy.