38

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.

8
  • 4
    Nice question. I was expecting a map() method on MutexGuard, like Ref::map()... Why isn't there one? *_* Commented Oct 17, 2016 at 20:54
  • 1
    ``` impl MyStruct { fn with_data<F : Fn(&Vec<i32))> (f : F) { ... } } ``` Would that work? Basically just let the user provide a function that will modify that data when under lock, instead of trying to return it?
    – dpc.pw
    Commented Oct 17, 2016 at 21:33
  • 1
    Closure method is commonly used through rust code for references, borrows etc.
    – dpc.pw
    Commented Oct 17, 2016 at 22:03
  • 1
    It's kind of possible. This code is not very nice tho, because it will do the lookup every time deref() is called. I don't think you can do any better in safe Rust. But I'd love to be proven wrong here. Commented Oct 17, 2016 at 22:56
  • 1
    @LukasKalbertodt I think you have the right idea but the wrong approach, I think you need a struct{MutexGuard<'a>,&'a Inner} with a deref(_mut) and map method. That should allow arbitrary remapping without waiting for a lock every time in safe Rust.
    – Linear
    Commented Oct 18, 2016 at 6:08

5 Answers 5

21

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.

1
  • 2
    This answer is so underrated. Thanks, this is exactly what I was looking for, and I'd never think to look for it in parking_lot. Commented Sep 22, 2020 at 18:59
10

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.

3
  • The library has a type called MutexGuardRef why not use that? kimundi.github.io/owning-ref-rs/owning_ref/…
    – Neikos
    Commented Oct 18, 2016 at 9:40
  • Thanks, I hadn't noticed MutexGuardRef! Commented Oct 18, 2016 at 10:07
  • 2
    @ChrisEmerson Is it possible to modify get_data_for method so it would not unwrap the result of get function and would return Option? I tried to do it myself but cannot get around conflicting lifetime requirements errors
    – Sergey
    Commented Dec 10, 2017 at 16:36
8

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
    }
}
1
  • 1
    Thanks for your answer! I decided to mark Chris' answer as accepted, because owning_ref does not require to make an intermediate type. But more importantly, as Lukas pointed in similar suggestion in comments, this will invoke map() closure on each deref(). On other hand, owning_ref invokes lookup only once (at least in cases I have tested). Commented Oct 18, 2016 at 17:49
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);
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 Derefs 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());
}

Not the answer you're looking for? Browse other questions tagged or ask your own question.