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.
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.
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
}
}
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());
}