References confusion and misunderstanding

How can I get this to work? It is a contrived example but doesn't compile. This is the error:

return map.get(&key).unwrap();
   |                ---^^^^^^^^^^^^^^^^^^^
   |                |
   |                returns a value referencing data owned by the current function
   |                `map` is borrowed here

How can I fix this the right way? Thanks.

use std::sync::{RwLock};
use std::collections::HashMap;

struct Outer
{
    outer : RwLock<HashMap<String, String>>
}
impl Outer
{
    fn new() -> Self
    {
        return Self {
            outer : RwLock::new(HashMap::new())
        }
    }
    
    fn add(&mut self, key : String, value : String) -> ()
    {
        let mut map = self.outer.write().unwrap();
        map.insert(key, value);
    }
    
    fn get(&self, key : String) -> &str
    {
        let map = self.outer.read().unwrap();
        return map.get(&key).unwrap();
    }
}

Could you rewrite that as:

let t = map.get(&key).unwrap();
return t;

?

I am also confused by the compiler error and want to see if the error is due to unwrap() or due to the return.

Why even have the RwLock there at all?
I don't see any attributes on the struct, so what is the RwLock used for?

The problem is that the call to read returns a value of type RwLockReadGuard. The existence of this type is what lets you access the RwLock contents, and when the guard goes out of scope, your lock of the RwLock is released, and it's unsafe to access its contents after that. The error is because you are trying to do exactly that.

4 Likes

So how do I fix this? Typically, in other languages you grab a read lock get a reference to what is in the map and hand it out. How do I achieve this in this situation. Thanks

You can return the RwLockReadGuard from the function.

There are also some other options. For example, you could clone the value before returning it:

fn get(&self, key: &str) -> String {
    let map = self.outer.read().unwrap();
    map.get(key).unwrap().clone()
}

Another option is to have the caller pass in a function which runs while the value is locked. That looks like this:

fn with_read_lock<F, T>(&self, key: &str, function: F) -> T
where
    F: FnOnce(&str) -> T,
{
    let map = self.outer.read().unwrap();
    function(map.get(key).unwrap())
}

You use it like this:

fn main() {
    let outer = Outer::new();
    
    outer.add("foo".to_string(), "bar".to_string());
    
    outer.with_read_lock("foo", |value| {
        println!("{}", value);
    });
}

Unrelated, but when you use an RwLock, you shouldn't mark the modification methods as &mut self. You can mark them &self without issue.

2 Likes

This looks like a duplicate of your previous thread. Were you able to try any of the suggestions there?

1 Like

Ah you give me too much credit. I am still trying to wrap my head around those suggestions. I am further simplifying things for myself and will get back to that soon. Thanks