Reputation: 1263
I have a struct Obj
with a large API that I would also like to use through an Arc<RwLock<Obj>>
. I have defined a struct ObjRef(Arc<RwLock<Obj>>)
and want to call functions of Obj
on ObjRef
without needing to call .read().unwrap()
or .write().unwrap()
every time. (ObjRef
implements Deref<Target=Arc<RwLock<Obj>>
, so I only have to call objRef.read().unwrap()
)
I could implement all of the functions of Obj
again for ObjRef
in a manner like this:
impl ObjRef {
fn foo(&self, arg: Arg) -> Ret {
self.read().unwrap().foo(arg)
}
}
But doing this for every function results in a lot of boilerplate.
Implementing Deref<Target=Obj>
for ObjRef
does not work, because the deref
implementation would be returning a reference into the RwReadLockGuard
object returned by .read().unwrap()
which would be dropped after deref
:
impl Deref for ObjRef {
type Target = Obj;
fn deref(&self) -> &Self::Target {
let guard: RwLockReadGuard<'_, Obj> = self.0.read().unwrap();
&*guard // <- return reference to Obj referenced by guard
// guard is dropped now, reference lifetime ends
}
}
Is there a way to call the Obj
Api through a low boilerplate implementation doing the locking internally?
I am thinking about introducing a trait for the Api with a default implementation of the Api and a single function to be implemented by users of the trait for getting a generic Deref<Target=Obj>
, but this puts the Api under some limitations, for example using generic parameters will become much more complicated.
Is there maybe a better way, something that lets me use any Obj
through a lock without explicitly calling the locking mechanism every time?
Upvotes: 1
Views: 902
Reputation: 43773
It is not possible to do this through Deref
, because Deref::deref
always returns a plain reference — the validity of it must only depend on the argument continuing to exist (which the borrow checker understands), not on any additional state such as "locked".
Also, adding locking generically to an interface designed without it is dangerous, because it may accidentally cause deadlocks, or allow unwanted outcomes due to doing two operations in sequence that should have been done using a single locking.
I recommend considering making your type internally an Arc<RwLock
, that is,
pub struct Obj {
lock: Arc<RwLock<ObjData>>,
}
// internal helper, not public
struct ObjData {
// whatever fields Obj would have had go here
}
Then, you can define your methods once, directly on Obj
, and the caller never needs to deal with the lock explicitly, but your methods can handle it exactly as needed. This is a fairly common pattern in problem domains that benefit from it, like UI objects.
If there is some reason why Obj
really needs to be usable with or without the lock, then define a trait.
Upvotes: 3