如何返回对互斥锁下的值的子值的引用?

Ser*_*ich 15 rust

我有一个看起来像这样的结构:

pub struct MyStruct {
    data: Arc<Mutex<HashMap<i32, Vec<i32>>>>,
}
Run Code Online (Sandbox Code Playgroud)

我可以很容易地锁定互斥锁并查询底层HashMap:

let d = s.data.lock().unwrap();
let v = d.get(&1).unwrap();
println!("{:?}", v);
Run Code Online (Sandbox Code Playgroud)

现在我想创建一个封装查询的方法,所以我写了这样的东西:

impl MyStruct {
    pub fn get_data_for(&self, i: &i32) -> &Vec<i32> {
        let d = self.data.lock().unwrap();
        d.get(i).unwrap()
    }
}
Run Code Online (Sandbox Code Playgroud)

这无法编译,因为我试图在以下情况下返回对数据的引用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();
   |                                           ^
Run Code Online (Sandbox Code Playgroud)

我可以通过将HashMap值包装在一个中来修复它Arc,但它看起来很丑(Arcin Arc)并使代码复杂化:

pub struct MyStruct {
    data: Arc<Mutex<HashMap<i32, Arc<Vec<i32>>>>>,
}
Run Code Online (Sandbox Code Playgroud)

解决这个问题的最佳方法是什么?是否有可能在不修改数据结构的情况下制作出我想要的方法?

完整的示例代码.

Nei*_*kos 7

这可以通过使用实现Deref和保存 MutexGuard的辅助结构来实现。

例子:

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
    }
}
Run Code Online (Sandbox Code Playgroud)


Chr*_*son 6

此解决方案类似于@Neikos的解决方案,但是使用owning_ref来保存MutexGuard和对的引用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);
    }
}
Run Code Online (Sandbox Code Playgroud)

这样做的好处是,可以轻松地(通过上的map方法owning_ref)从Mutex((Vec等)中的单个项目)获取对其他可访问对象的相似引用,而不必重新实现返回的类型。

  • @ChrisEmerson是否可以修改“get_data_for”方法,使其不会“解包”“get”函数的结果并返回“Option”?我尝试自己做,但无法解决“生命周期要求冲突”错误 (2认同)

Nie*_*kob 5

parking_lot箱提供互斥的实现,那最好不过了许多帐户一个比std。好东西是MutexGuard::map,它实现了一个类似于 的接口owning_ref

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())
    }
}
Run Code Online (Sandbox Code Playgroud)

你可以在这里的操场上试一试。

  • 这个答案被低估了。谢谢,这正是我一直在寻找的东西,我从来没有想过要在“parking_lot”中寻找它。 (2认同)

Pi *_*ort 5

这是评论中提到的闭包传递方法的实现:

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))
    }
}
Run Code Online (Sandbox Code Playgroud)

铁锈游乐场

用法示例:

s.with_data_for(&1, |v| {
    println!("{:?}", v);
});
Run Code Online (Sandbox Code Playgroud)
let sum: i32 = s.with_data_for(&1, |v| v.iter().sum()).unwrap();
println!("{}", sum);
Run Code Online (Sandbox Code Playgroud)