如何使用对由Arc <rwlock <t >>保护的基础数据的引用?

时间:2018-07-05 04:19:58

标签: rust

通过内部可变性模式获得的任何引用(无论是Rc<RefCell<T>>还是Arc<RwLock<T>>)都必须在封闭函数返回时被丢弃。当您想使用此引用返回对该RefCellRwLock保护的某些数据的引用时,则不允许这样做。

我认为不允许这样做是正确的:因为我正在使用内部可变性,所以任何与“内部”相关联的引用都应委派给运行时检查器,因此不应将纯引用&T委托给运行时检查器。暴露在外面。

但是,除了用Rc<RefCell<T>>包装基础数据之外,我没有找到一个具有相同目的的好选择。

说明问题的代码:

use std::string::String;
use std::sync::{Arc, RwLock};

struct MyStruct {
    data: MyData,
}

struct MyData {
    val: String,
}

fn main() {
    let my_struct = MyStruct {
        data: MyData {
            val: String::from("hi"),
        },
    };
    let obj = Arc::new(RwLock::new(my_struct));

    let data = get_data_ref(&obj);
    println!("{}", data);
}

fn get_data_ref(obj: &Arc<RwLock<MyStruct>>) -> &String {
    let obj = Arc::clone(obj);

    let data = obj.read().unwrap(); //data is created
    data.get_data()
} //data dropped here, thus lives not long enough.

impl MyStruct {
    fn get_data(&self) -> &String {
        self.data.get_val()
    }
}

impl MyData {
    fn get_val(&self) -> &String {
        &self.val
    }
}

playground

1 个答案:

答案 0 :(得分:2)

这里有2个问题:

  1. 终身
  2. 内部参考

让我们按顺序解决它们。

终身

首先,存在一个从let obj = Arc::clone(obj);继承的生存期问题。

您要将所有生存期引用都锚定到变量,而不是直接从参数中直接读取。

您可以NEVER return a reference to a local variable;所以我们不要。只需删除该行即可:

fn get_data_ref(obj: &Arc<RwLock<MyStruct>>) -> &String {
    let data = obj.read().unwrap();
    data.get_data()
}

哪个更好了?

内部参考

第二个问题是内部参考。 RwLock.read()返回一个 guard ,它将在其析构函数中解锁。作为回报,防护程序仅返回生存期最长的引用,从而确保在防护程序被销毁(并且锁已解锁)之后不会进行任何访问。

不幸的是,没有办法从RwLockReadGuard<'a, MyStruct>转到某种RwLockReadGuardInner<'a, String>,这会神奇地授予您对MyStruct字段的访问权限。

从理论上讲这是可能的,但尚未对此提供支持。


如果您想使其发挥作用...

RwLockReadGuardInner<'a, String>的思想本质上是:

struct RwLockReadGuardInner<'a, T> {
    lock: *const sys::RWLock,
    poison: *const poison::Flag,
    data: &'a T,
}

impl<'a, T> Drop for RwLockReadGuardInner<'a, T> {
    //  magic happens
}

然后,您将在map上执行RwLockReadGuard操作:

impl<'a, T> RwLockReadGuard<'a, T> {
    fn map<F, U>(self, f: F) -> RwLockReadGuardInner<'a, U>
    where
        F: FnOnce(&T) -> &U,
    {
        let result = RwLockReadGuardInner {
            lock: &self.__lock.inner as *const _,
            poison: &self.__lock.poison as *const _,
            data: f(unsafe { &*self.__lock.data.get() }),
        };

        std::mem::forget(self);

        result
    }
}

我发现有趣的是,稍微更改RwLock的定义(将innerpoison包装在单个结构中)将允许更改RwLockReadGuard来都引用主场和任何内部场(以其大小加倍为代价)。

如果愿意,可以制定RFC来支持这种情况。 RwLockReadGuard并不是唯一可能对此有用的方法,大多数内部可变性保护措施将从这种map操作中受益。