rustborrow-checkerrwlock

Returning a RWLockReadGuard independently from a method


I have an object of type

Arc<RwLock<SessionData>>

And I have a method that is supposed to take some kind of reference to SessionData

fn some_method(session: ...)

I'm using Rocket (a web-framework for Rust), and I can't directly invoke the method, because it is invoked by Rocket. However, I can provide it with an implementation that creates an object that will be passed to the handler. It looks a bit like this:

impl<'a, 'r> request::FromRequest<'a, 'r> for SomeType {
    type Error = ();

    fn from_request(request: &'a request::Request<'r>) -> request::Outcome<Self, Self::Error> {
        // return object here
    }
}

I want to avoid returning an RwLock directly, because I want the handler to have an already-locked object passed to it. However, I can't return a reference or a RwLockReadGuard, because both of them depend on the RwLock, which would go out of scope.

Instead, I am trying to create some kind of self-sufficient type that would contain an Arc<RwLock<SessionData>>, contain the lock guard to this lock, and deref to a SessionData object.

So far, I have tried some combinations of the following:

However, I haven't been able to do what I want to do, running into various lifetime borrowing issues and whatnot.

Is it at all possible to create a sort of a self-contained 'Handle'-like object that would contain both the lock and the lock guard to the object that it points to?

This is a similar, but slightly different situation than described in How to return reference to a sub-value of a value that is under a mutex?. In there, the MutexGuardRef internally depends on Mutex, and cannot exist if the Mutex (or MyStruct) goes out of scope. In order to achieve similar behaviour, I'd have to pass a struct that contains my RwLock and then do the locking inside the method. This is fine, but I'm wondering if I can go another step further, and pass a struct that is both independent and serves as a RwLockGuard, avoiding the need to lock manually.

Basically, I want to move the locking of the RwLock from the client to the provider of the value.


Solution

  • 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.

    #[macro_use]
    extern crate rental;
    
    use std::sync::{Arc, RwLock};
    
    struct SessionData;
    impl SessionData {
        fn hello(&self) -> u8 { 42 }
    }
    
    rental! {
        mod owning_lock {
            use std::sync::{Arc, RwLock, RwLockReadGuard};
    
            #[rental(deref_suffix)]
            pub struct OwningReadGuard<T>
            where
                T: 'static,
            {
                lock: Arc<RwLock<T>>,
                guard: RwLockReadGuard<'lock, T>,
            }
        }
    }
    
    use owning_lock::OwningReadGuard;
    
    fn owning_lock(session: Arc<RwLock<SessionData>>) -> OwningReadGuard<SessionData> {
        OwningReadGuard::new(session, |s| s.read().unwrap())
    }
    
    fn main() {
        let session = Arc::new(RwLock::new(SessionData));
    
        let lock = owning_lock(session.clone());
        println!("{}", lock.hello());
    
        assert!(session.try_read().is_ok());
        assert!(session.try_write().is_err());
    
        drop(lock);
    
        assert!(session.try_write().is_ok());
    }
    

    See also: