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?
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.