What is the cleanest way to have nested mutable borrows?

I am saving some data within self.inner unto the disk as so:

    /// Saves data within self unto disk via the default path
    pub fn save_to_disk(&mut self) -> Result<(), AccountError<String>> {
        let read = self.read_recursive(); // equivalent to self.inner.read_recursive()

        if let Some(drill) = read.toolset.get_most_recent_drill() {
            let (encrypted, key) = self.inner.write().password_in_ram.as_mut().unwrap();
            let (decrypted_bytes, key) = unsafe { encrypted.read(*key) };
            self.write().password_in_ram.as_mut().unwrap().1 = key;
            let path = read.local_save_path.clone();

            match read.password_hyxefile.drill_contents(drill, decrypted_bytes.as_slice(), SecurityLevel::DIVINE) {
                Ok(_) => self.save_to_local_fs(path),
                Err(err) => Err(AccountError::Generic(err.to_string()))
            }
        } else {
            return Err(AccountError::Generic("Unable to acquire most recent drill from toolset".to_string()));
        }
    }

inner is of type Arc<RwLock<_>>, and the inner type #Derive's[Serialize, Deserialize].

Of course, my code is giving the borrow-checker heart problems and brain aneurisms. What is a good way to setup nested mutable borrows, or, if needed, does my problem imply that I need to change-up the design patterns?

The code looks fine, you just have to avoid dropping the lock.

Variables in Rust are semantically meaningful!

foo().bar();

is different from

let tmp = foo();
tmp.bar();

In your case you need:

let keep_that_lock_working = self.inner.write();
let let (encrypted, key) = keep_that_lock_working.password_in_ram.as_mut().unwrap();

Otherwise the lock from write() gets dropped on the same line, and you can't use the locked value.

2 Likes

Thanks again Kornel. In case anyone wanted to see his solution in action, here it is:

    /// Saves data within self unto disk via the default path
    pub fn save_to_disk(&mut self) -> Result<(), AccountError<String>> {
        if let Some(drill) = self.read_recursive().toolset.get_most_recent_drill() {
            let mut write = self.inner.write();
            let (encrypted, key) = write.password_in_ram.as_mut().unwrap();
            let (decrypted_bytes, key) = unsafe { encrypted.read(*key) };
            write.password_in_ram.as_mut().unwrap().1 = key;
            let path = write.local_save_path.clone();

            match write.password_hyxefile.drill_contents(drill, decrypted_bytes.as_slice(), SecurityLevel::DIVINE) {
                Ok(_) => self.save_to_local_fs(path),
                Err(err) => Err(AccountError::Generic(err.to_string()))
            }
        } else {
            return Err(AccountError::Generic("Unable to acquire most recent drill from toolset".to_string()));
        }
    }

Also, using recursive reads is known to starve writers. Wouldn't this code lock-up in theory? I.e., dead-lock

I've just noticed that you have &mut self instead of &self typically used when locks are needed.

If your code works with &mut self, you can bypass the lock safely. Existence of &mut self is 100% perfect guarantee that there can't be anything else in the program holding this lock.

https://doc.rust-lang.org/std/sync/struct.RwLock.html#method.get_mut

1 Like

Hmm, get_mut() doesn't work in Arc?

Ahhh, nevermind then. Arc adds shared ownership, so the mutable/exclusive access of self no longer applies.

1 Like