简体   繁体   中英

Deref a Arc<RwLock<T>> to return RwLockReadGuard<T> by default

I have the following wrapper for Arc<RwLock<T>> , and I would like to deref them to return the RwLockReadGuard<T> by default.

use anyhow::{Result, bail};
use serde::{Deserialize, Serialize};
use std::ops::Deref;
use std::sync::{Arc, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard};

#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct ArcRwLock<T: Sized>(Arc<RwLock<T>>);

impl<T> ArcRwLock<T> {
    pub fn new(data: T) -> Self {
        ArcRwLock(Arc::new(RwLock::new(data)))
    }

    pub fn write(&self) -> Result<RwLockWriteGuard<T>> {
        match self.0.try_write() {
            Ok(x) => Ok(x),
            Err(e) => {
                bail!(
                    "RwLock cannot acquire writer lock, error: {}",
                    e.to_string()
                )
            }
        }
    }

    pub fn read(&self) -> RwLockReadGuard<T> {
        self.0.read().unwrap()
    }

}

// impl<T: Sized> Deref for ArcRwLock<T> {
//     type Target = RwLockReadGuard<T>;

//     #[inline]
//     fn deref(&self) -> &Self::Target {
//         self.0.read().unwrap()
//     }
// }

impl<T: PartialEq> PartialEq for ArcRwLock<T> {
    fn eq(&self, other: &Self) -> bool {
        if Arc::ptr_eq(&self.0, &other.0) && ::core::ptr::eq(&*self.0, &*other.0) {
            true
        } else {
            *other.0.read().unwrap().deref() == *self.0.read().unwrap().deref()
        }
    }
}

I wrote the above wrapper mainly for the PartialEq I need for a parent struct to correctly #[derive(PartialEq)] .

Most of the time I'm reading the values T from Arc<RwLock<T>> , writing to it is rare.

The above implementation allows me to read/write the values using:

some_arc_object.write()?.uuid = Uuid::new_v4();
let updated_uuid: T = some_arc_object.read().uuid;
// where uuid is a field of T

Since I'm reading the properties most of the time, I'd like to get rid of the repeated .read() and achieve the following by Dereferencing the entire Arc<RwLock>:

let updated_uuid: T = some_arc_object.uuid;
// instead of having to add .read()
// let updated_uuid: T = some_arc_object.read().uuid;

My current humble attempt is shown above in the commented section, trying to make deref() work the same way .read() does. But the compiler is not happy with returning a reference of a local variable. Is there any chance it could be achieved, either by some lifetime magic or other workaround?

Dereferences by design should only resolve smart pointers to the pointee, see eg the Rust API Guidelines . Acquiring a synchronization guard like MutexGuard or RwLockReadGuard in the Deref definitely goes beyond this guideline and could cause subtle bugs. Ie you can get implicit locking of a resource without ever explicitly calling lock() , read() or write() on it because it's hidden in the Deref impl which implicitly gets called when resolving a method call.

As for the reasoning why it's not possible: Deref 's return type is a reference, thus you need to return something that you can turn into a reference from deref() . The RwLockReadGuard is a value that you're creating inside the deref() scope, thus it's dropped at the end of the scope, which in turn means you're not allowed to hand out a reference to it.

Sometimes it can be ergonomic to wrap whatever you need to do with the value inside the RwLock inside a function, ie if it's a String that sometimes gets written to but most of the time you just want to read it, define some convenience method like the following:

struct Foo {
    shared: Arc<RwLock<String>>,
}
impl Foo {
    fn get_shared_str(&self) -> String {
        self.shared.read().unwrap().clone()
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM