ruststatichashmapmutexlazy-static

Get item's reference from global HashMap in Rust


I'm trying to use a static HashMap<String, Object> to store some data that I want to use and modify globally in the future. I found out that some way of declaring such a global map is by using lazy_static and mutex in order to share data safely. However, I'm getting some ownership troubles when I want to return these objects as reference, as I do in the code above:

use std::error::Error;
use std::collections::HashMap;
use std::sync::Mutex;
use super::domain::{Session, SessionRepository}; // object and trait declaration

lazy_static! {
    static ref REPOSITORY: Mutex<HashMap<String, Session>> = {
        let mut repo = HashMap::new();
        Mutex::new(repo)
    };    
}

impl SessionRepository for REPOSITORY {
    fn find(cookie: &str) -> Result<&mut Session, Box<dyn Error>> {
        let mut repo = REPOSITORY.lock()?;
        if let Some(sess) = repo.get_mut(cookie) {
            return Ok(sess);
        }

        Err("Not found".into())
    }
}

So the question is: Is there any way of doing this correctly? Does it exists any design pattern in Rust I could use in order to reach this behavior?

Thank you very much!


Solution

  • What you're trying to do is correctly rejected by the compiler, because if you could return a reference, then bad things can happen. Since after the function returns, the mutex is no longer locked,

    1. Some other thread could lock the mutex and get its own mutable reference to the session, violating the rule that mutable references provide exclusive access.
    2. Some other thread could lock the mutex and remove the session from the HashMap — now you're accessing deallocated memory.

    Solution: each session should be in its own Arc<Mutex<_>>. That is:

    lazy_static! {
        static ref REPOSITORY: Mutex<HashMap<String, Arc<Mutex<Session>>>> = {
            ...
    
    impl SessionRepository for REPOSITORY {
        fn find(cookie: &str) -> Result<Arc<Mutex<Session>>, Box<dyn Error>> {
            let mut repo = REPOSITORY.lock()?;
            if let Some(sess) = repo.get(cookie) {
                return Ok(Arc::clone(sess));
            }
            ...
        }
    

    The Arc allows the session to be kept alive both by the repository and whichever threads have called find() on it and thus gotten their own clone of the Arc reference-counted pointer. The Mutex allows each session to be independently mutated.