Dávid Tóth
Dávid Tóth

Reputation: 3235

Rust destructure enum protected by std::sync::RwLock, return with reference

I have a vector of enum elements I'd like to access in a thread-safe manner. I have interfaces that need to degrade the enum values to an option, as to not expose the inner structures to the public API. For that I have enclosed each of the vector elements into an RwLock:

enum Container<T>{
    Empty, Emptied, Full(T)
}

use std::sync::{RwLock, RwLockReadGuard};

struct Ship<T>{
    content: Vec<RwLock<Container<T>>>
}

impl<T> Ship<T>{

    pub fn get_enum(&self, index: usize) -> Option<RwLockReadGuard<'_, Container<T>>>{
       self.content[index].read().ok()
    }

    // The below code fails to compile: 
    // pub fn get(&self, index: usize) -> Option<&T>{
    //   match self.content[index].borrow().ok().unwrap() {
    //       Container::Full(c) => Some(c), 
    //       _=> None
    //   }
    // }
    
    pub fn get_mut(&mut self, index: usize) -> Option<&mut T>{
       match self.content[index].get_mut().ok().unwrap() {
           Container::Full(c) => Some(c), 
           _=> None
       }
    }
}

The above seem to compile, but is it safe code to run? I would also like to provide an interface for read-only access, but I failed to find the corresponding function in the API. For that what can be used?

Upvotes: 2

Views: 151

Answers (1)

cafce25
cafce25

Reputation: 27303

It is certainly safe to run, after all Rust guarantees that if you don't use unsafe it's memory and thread safe. But it most certainly doesn't do what you want it to, since get_mut requires you to only have one reference to the RwLock but in that case you could just forgo the RwLock alltogether and have the same effect, but that doesn't let you share a Ship across threads, it requires an exclusive reference.

You likely want to use a RwLock that supports mapping on it's guards such as the one from parking_lot:

use parking_lot::{MappedRwLockWriteGuard, RwLock, RwLockReadGuard, RwLockWriteGuard};

/* ... */

impl<T> Ship<T> {
    pub fn get_enum(&self, index: usize) -> Option<RwLockReadGuard<'_, Container<T>>> {
        self.content.get(index).map(|g| g.read())
    }

    pub fn get_mut(&self, index: usize) -> Option<MappedRwLockWriteGuard<'_, T>> {
        RwLockWriteGuard::try_map(self.content.get(index)?.write(), |c| match c {
            Container::Full(c) => Some(c),
            _ => None,
        })
        .ok()
    }
}

Playground

Upvotes: 3

Related Questions