How to return a reference to a sub-value of a value that is under a mutex?
I have a structure that looks somewhat like this:
pub struct MyStruct {
data: Arc<Mutex<HashMap<i32, Vec<i32>>>>,
}
I can easily get a lock on the mutex and query the underlying HashMap
:
let d = s.data.lock().unwrap();
let v = d.get(&1).unwrap();
println!("{:?}", v);
Now I want to make a method to encapsulate the querying, so I write something like this:
impl MyStruct {
pub fn get_data_for(&self, i: &i32) -> &Vec<i32> {
let d = self.data.lock().unwrap();
d.get(i).unwrap()
}
}
This fails to compile because I'm trying to return a reference to the data under a Mutex
:
error: `d` does not live long enough
--> <anon>:30:9
|
30 | d.get(i).unwrap()
| ^
|
note: reference must be valid for the anonymous lifetime #1 defined on the block at 28:53...
--> <anon>:28:54
|
28 | pub fn get_data_for(&self, i: &i32) -> &Vec<i32> {
| ^
note: ...but borrowed value is only valid for the block suffix following statement 0 at 29:42
--> <anon>:29:43
|
29 | let d = self.data.lock().unwrap();
| ^
I can fix it by wrapping the HashMap
values in an Arc
, but it looks ugly (Arc
in Arc
) and complicates the code:
pub struct MyStruct {
data: Arc<Mutex<HashMap<i32, Arc<Vec<i32>>>>>,
}
What is the best way to approach this? Is it possible to make a method that does what I want, without modifying the data structure?
Full example code.
Solution 1:
This solution is similar to @Neikos's, but using owning_ref to do hold the MutexGuard
and a reference to the Vec
:
extern crate owning_ref;
use std::sync::Arc;
use std::sync::{Mutex,MutexGuard};
use std::collections::HashMap;
use std::vec::Vec;
use owning_ref::MutexGuardRef;
type HM = HashMap<i32, Vec<i32>>;
pub struct MyStruct {
data: Arc<Mutex<HM>>,
}
impl MyStruct {
pub fn new() -> MyStruct {
let mut hm = HashMap::new();
hm.insert(3, vec![2,3,5,7]);
MyStruct{
data: Arc::new(Mutex::new(hm)),
}
}
pub fn get_data_for<'ret, 'me:'ret, 'c>(&'me self, i: &'c i32) -> MutexGuardRef<'ret, HM, Vec<i32>> {
MutexGuardRef::new(self.data.lock().unwrap())
.map(|mg| mg.get(i).unwrap())
}
}
fn main() {
let s: MyStruct = MyStruct::new();
let vref = s.get_data_for(&3);
for x in vref.iter() {
println!("{}", x);
}
}
This has the advantage that it's easy (through the map
method on owning_ref
) to get a similar reference to anything else reachable from the Mutex
(an individual item in a Vec
, etc.) without having to re-implement the returned type.
Solution 2:
The parking_lot
crate provides an implementation of Mutexes that's better on many accounts than the one in std
. Among the goodies is MutexGuard::map
, which implements an interface similar to owning_ref
's.
use std::sync::Arc;
use parking_lot::{Mutex, MutexGuard, MappedMutexGuard};
use std::collections::HashMap;
pub struct MyStruct {
data: Arc<Mutex<HashMap<i32, Vec<i32>>>>,
}
impl MyStruct {
pub fn get_data_for(&self, i: &i32) -> MappedMutexGuard<Vec<i32>> {
MutexGuard::map(self.data.lock(), |d| d.get_mut(i).unwrap())
}
}
You can try it on the playground here.
Solution 3:
This can be made possible by using a secondary struct that implements Deref
and holds the MutexGuard.
Example:
use std::sync::{Arc, Mutex, MutexGuard};
use std::collections::HashMap;
use std::ops::Deref;
pub struct Inner<'a>(MutexGuard<'a, HashMap<i32, Vec<i32>>>, i32);
impl<'a> Deref for Inner<'a> {
type Target = Vec<i32>;
fn deref(&self) -> &Self::Target {
self.0.get(&self.1).unwrap()
}
}
pub struct MyStruct {
data: Arc<Mutex<HashMap<i32, Vec<i32>>>>,
}
impl MyStruct {
pub fn get_data_for<'a>(&'a self, i: i32) -> Inner<'a> {
let d = self.data.lock().unwrap();
Inner(d, i)
}
}
fn main() {
let mut hm = HashMap::new();
hm.insert(1, vec![1,2,3]);
let s = MyStruct {
data: Arc::new(Mutex::new(hm))
};
{
let v = s.get_data_for(1);
println!("{:?}", *v);
let x : Vec<_> = v.iter().map(|x| x * 2).collect();
println!("{:?}", x); // Just an example to see that it works
}
}
Solution 4:
As described in Why can't I store a value and a reference to that value in the same struct?, the Rental crate allows for self-referential structs in certain cases. Here, we bundle the Arc
, the MutexGuard
, and the value all into a struct that Deref
s to the value:
#[macro_use]
extern crate rental;
use std::{
collections::HashMap, sync::{Arc, Mutex},
};
use owning_mutex_guard_value::OwningMutexGuardValue;
pub struct MyStruct {
data: Arc<Mutex<HashMap<i32, Vec<i32>>>>,
}
impl MyStruct {
pub fn get_data_for(&self, i: &i32) -> OwningMutexGuardValue<HashMap<i32, Vec<i32>>, Vec<i32>> {
OwningMutexGuardValue::new(
self.data.clone(),
|d| Box::new(d.lock().unwrap()),
|g, _| g.get(i).unwrap(),
)
}
}
rental! {
mod owning_mutex_guard_value {
use std::sync::{Arc, Mutex, MutexGuard};
#[rental(deref_suffix)]
pub struct OwningMutexGuardValue<T, U>
where
T: 'static,
U: 'static,
{
lock: Arc<Mutex<T>>,
guard: Box<MutexGuard<'lock, T>>,
value: &'guard U,
}
}
}
fn main() {
let mut data = HashMap::new();
data.insert(1, vec![1, 2, 3]);
let s = MyStruct {
data: Arc::new(Mutex::new(data)),
};
let locked_data = s.get_data_for(&1);
let total: i32 = locked_data.iter().map(|x| x * 2).sum();
println!("{}", total);
assert!(s.data.try_lock().is_err());
drop(locked_data);
assert!(s.data.try_lock().is_ok());
}
Solution 5:
Here's an implementation of the closure-passing approach mentioned in the comments:
impl MyStruct {
pub fn with_data_for<T>(&self, i: &i32, f: impl FnOnce(&Vec<i32>) -> T) -> Option<T> {
let map_guard = &self.data.lock().ok()?;
let vec = &map_guard.get(i)?;
Some(f(vec))
}
}
Rust Playground
Example usage:
s.with_data_for(&1, |v| {
println!("{:?}", v);
});
let sum: i32 = s.with_data_for(&1, |v| v.iter().sum()).unwrap();
println!("{}", sum);