35

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 4
    Nice question. I was expecting a `map()` method on `MutexGuard`, like [`Ref::map()`](https://doc.rust-lang.org/std/cell/struct.Ref.html#method.map)... Why isn't there one? \*\_\* – Lukas Kalbertodt Oct 17 '16 at 20:54
  • 1
    ``` impl MyStruct { fn with_data (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 Oct 17 '16 at 21:33
  • @dpc.pw Yep. I thought about closure approach too... Not very beautiful, but does what I need... The best solution would be something like `map()` as Lukas mentioned, or some kind of weird lifetime manipulation magic, but I can't think of any... – Sergey Mitskevich Oct 17 '16 at 21:44
  • 1
    Closure method is commonly used through rust code for references, borrows etc. – dpc.pw Oct 17 '16 at 22:03
  • 1
    [It's kind of possible](https://play.rust-lang.org/?gist=b2e483fa6b65d762f03b7137cbe686a0&version=stable&backtrace=0). 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. – Lukas Kalbertodt Oct 17 '16 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 Oct 18 '16 at 06:08
  • 1
    @Jsor I'd love to see that in action ;-) – Lukas Kalbertodt Oct 18 '16 at 07:05
  • @LukasKalbertodt [Evidently `MutexGuard::map` would be unsound](https://github.com/rust-lang/rust/pull/30834#issuecomment-180284290) – Shepmaster Aug 03 '18 at 03:45

5 Answers5

19

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.

Maya
  • 1,490
  • 12
  • 24
  • 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`. – user4815162342 Sep 22 '20 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.

Chris Emerson
  • 13,041
  • 3
  • 44
  • 66
  • The library has a type called `MutexGuardRef` why not use that? http://kimundi.github.io/owning-ref-rs/owning_ref/type.MutexGuardRef.html – Neikos Oct 18 '16 at 09:40
  • Thanks, I hadn't noticed `MutexGuardRef`! – Chris Emerson Oct 18 '16 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 Dec 10 '17 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
    }
}
Neikos
  • 1,840
  • 1
  • 22
  • 35
  • 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). – Sergey Mitskevich Oct 18 '16 at 17:49
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());
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
4

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);
Pi Delport
  • 10,356
  • 3
  • 36
  • 50