Refael Sheinker
Refael Sheinker

Reputation: 891

Returning a mutable reference to a value behind Arc and Mutex

pub struct ForesterViewModel {
    m_tree_lines: Arc<Mutex<Vec<TreeLine>>>,
}

impl ForesterViewModel {
    pub fn new() -> ForesterViewModel {
        ForesterViewModel {
            m_tree_lines: Arc::new(Mutex::new(vec![])),
        }
    }

    pub fn get_the_forest(&mut self) -> &mut Vec<TreeLine> {
        ???????????????????????????????
    }
}

I need help writing the get_the_forest function. I've tried many various things but they all return compilation errors. I need to return a mutable reference to Vec<TreeLine> which is wrapped behind an Arc and a Mutex in self.m_tree_lines.

Upvotes: 5

Views: 3725

Answers (1)

Doug
Doug

Reputation: 35106

There is no way of doing this.

You create a concrete MutexGuard object that releases the mutex when it dropped when you call lock; you cannot move a reference out of the scope that contains the guard:

pub fn as_mut(&mut self) -> &Whatever {
  let mut guard = self.data.lock().unwrap();
  Ok(guard.deref())
  drop(guard) // <--- implicitly added here, which would invalidate the ref
}

You also cannot return both the mutex guard and a reference, for more complex reasons (basically rust cannot express that), for the same reason it cannot have a reference and an object in a single structure; see the discussion on Why can't I store a value and a reference to that value in the same struct?

...so basically your best bet is one of two things:

/// Return the mutex guard itself
pub fn get_the_forest(&mut self) -> Result<MutexGuard<Vec<TreeLine>>, TreeLockError> {
    Ok(self.m_tree_lines.lock()?)
}
/// Pass a function in, which patches the mutable internal value
pub fn patch_forest(&mut self, patch: impl Fn(&mut Vec<TreeLine>)) -> Result<(), TreeLockError>{
    let mut guard = self.m_tree_lines.lock()?;
    patch(&mut guard); // <-- patch happens while guard is still alive
    Ok(())
}

Full code:

use std::sync::{Arc, Mutex, MutexGuard};
use std::sync::PoisonError;
use std::error::Error;
use std::fmt;
use std::fmt::Formatter;
use std::ops::Deref;

#[derive(Debug, Copy, Clone)]
pub enum TreeLockError {
    FailedToLock
}

impl Error for TreeLockError {}

impl fmt::Display for TreeLockError {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(f, "{:?}", self)
    }
}

impl<T> From<PoisonError<T>> for TreeLockError {
    fn from(_: PoisonError<T>) -> Self {
        TreeLockError::FailedToLock
    }
}

// ---

#[derive(Debug)]
pub struct TreeLine {
    pub value: &'static str
}

pub struct ForesterViewModel {
    m_tree_lines: Arc<Mutex<Vec<TreeLine>>>,
}

impl ForesterViewModel {
    pub fn new() -> ForesterViewModel {
        ForesterViewModel {
            m_tree_lines: Arc::new(Mutex::new(vec![])),
        }
    }

    pub fn get_the_forest(&mut self) -> Result<MutexGuard<Vec<TreeLine>>, TreeLockError> {
        Ok(self.m_tree_lines.lock()?)
    }
    
    pub fn patch_forest(&mut self, patch: impl Fn(&mut Vec<TreeLine>)) -> Result<(), TreeLockError>{
        let mut guard = self.m_tree_lines.lock()?;
        patch(&mut guard);
        Ok(())
    }
}

fn main() -> Result<(), Box<dyn Error>> {
    let mut vm = ForesterViewModel::new();
    
    {
        let mut trees = vm.get_the_forest()?;
        trees.push(TreeLine{ value: "one"});
        trees.push(TreeLine{ value: "two"});
    } // <--- Drop the mutable reference here so you can get it again later
    
    // Patch
    vm.patch_forest(|trees| {
        trees.push(TreeLine{ value: "three"}); 
    });
    
    // ...
    let trees = vm.get_the_forest()?;
    println!("{:?}", trees.deref());
    
    Ok(())
}

Upvotes: 4

Related Questions