3
votes

I've been working on my first Rust project recently but have hit a snag. I am using a HashMap mapping Strings to AtomicUsize integers. The HashMap is protected by a RwLock to allow for concurrent access. I would like to be able to return references to AtomicUsize values in the HashMap, however if I try to return these references to the caller past the lifetime of the RwLockWriteGuard I get an error that borrowed value does not live long enough. I've reproduced a minimal example below and put the same example on the Rust playground here.

use std::collections::HashMap;
use std::sync::RwLock;
use std::sync::atomic::{AtomicUsize, Ordering};

struct Bar {
    val: AtomicUsize
}

impl Bar {
    pub fn new() -> Self {
        Bar { val: AtomicUsize::new(0) }
    }
}

struct Foo {
    map: RwLock<HashMap<String, Bar>>
}


impl Foo {
    pub fn get(&self, key: String) -> &Bar {
        self.map.write().unwrap().entry(key).or_insert(Bar::new())
    }
}

fn main() {
    let foo = Foo {map: RwLock::new(HashMap::new())};
    let bar = foo.get("key".to_string());
}

The error I get occurs on the line:

self.map.write().unwrap().entry(key).or_insert(Bar::new())

And is because the borrowed value does not live long enough. I've read a few other posts that discuss this error, this one in particular was especially relevant. After reading it over, I can gather that a value returned from a mutex must have a lifetime less than that of the mutex, which would seem to rule out completely what I'm trying to do. I can see why this should be impossible, because if we have a pointer into the Hashmap and another inserts values into the mutex which cause it to be resized, then we will have a dangling pointer.

My question, then, is twofold. Firstly, I'm just curious if I understand the problem correctly or if there is another reason why I'm disallowed from doing what I tried to do? And my second question is if there is perhaps another way to accomplish what I am trying to do without Box the atomic integers and storing those in the HashMap? Such an approach seems like it should work to me because we can return a pointer to the Boxed value which would always be valid. However it seems like this approach would be inefficient because it would require an extra layer of pointer indirection and an extra allocation. Thanks!

1
In addition to the map resizing, someone else could simply delete the entry you're holding a reference to.Sebastian Redl
You're exactly right! For my use case I don't need to ever delete a key, but of course the compiler doesn't know that.jeromefroe

1 Answers

8
votes

You're correct that you can't return a reference to something which outlives the MutexGuard, because that would lead to a possibly dangling pointer.

Wrapping the contents in a Box won't help, though! A Box is an owned pointer and apart from the redirection behaves like the contained value as far as reference lifetime goes. After all, if you returned a reference to it, someone else might remove it from the HashMap and de-allocate it.

Depending on what you want to do with the reference, I can think of a couple of options:

  1. Instead of Boxing the values, wrap them in Arc. You would clone the Arc when taking from the HashMap, and multiple references can live at the same time.

  2. You could also return the MutexGuard along with the reference; see this question, which would work well if you just want to operate on the value and then drop the reference relatively soon. This would keep the mutex held until you're finished with it.