How to solve borrow self as mutable more than once in this case?


#1

I am trying to manipulate hashmap field of a struct and continue to use struct variable in the following up code. Could you please help me to overcome this compiler error in this case?:

Here is the playground

use std::collections::HashMap;

fn main() {
    let mut d = Data::default();
    d.mutate();
}

#[derive(Default)]
struct Cache {
    field: i32
}

#[derive(Default)]
struct Data {
    map: HashMap<i32, Cache>
}

impl Data {
    fn mutate(&mut self) -> () {
        let c = self.map.entry(0).or_insert_with(|| Cache::default());
        //if self.field.is_empty() { // <-- this also does not work for the compiler
        if self.read_only_helper() { // <-- this does not work for the compiler
            c.field = 1;
        }
    }
    fn read_only_helper(&mut self)->bool {
        // returns something dummy for example
        self.map.is_empty()
    }
}


#2

So, just to basically restate what the compiler is telling you, the problem here is that you’re trying to (immutably) borrow something (self.map in this case) after you’re already mutably borrowed it and this is something that rust will explicitly prevent you from doing as this can cause problems.

A way to work around this in this particular case would be to get the check if the map is empty out of the way before you borrow the entry out of the map. This is technically a logical change, but what you have doesn’t really make much sense because even if your map is empty you’re or_insert_with(...)'ing so it couldn’t possibly still be empty when you are trying to check.

Code for what I mean:

impl Data {
    fn mutate(&mut self) -> () {
        let map_is_empty = self.map.is_empty();
        let c = self.map.entry(0).or_insert_with(|| Cache::default());
        if map_is_empty {
            c.field = 1;
        }
    }
}

#3

Ohh, this was simplified example. where I tried to demonstrate calls to self in the order. I can not change the order (although in this specific example it is possible). I need to obtain cache entry from self, and continue to use self (i.e. call it’s methods) and use cache entry (to mutate it) in parallel.


#4

This is going to be difficult because in accordance with the encapsulation principle, using any method of self is understood by the compiler to borrow and potentially access all of it, including the inner cache to which you are holding a mutable reference.

There are two common strategies for solving this kind of issue:

  • Drop one layer of abstraction lower, and use fields directly instead of calling object-wide methods. This exposes the disjointness of the underlying borrows (there are periodical discussions about how we could extend method interfaces to similarly expose disjointness, nothing concrete yet though)
  • Move the cache outside of self (there are various possibilities for this, including moving it on the heap with Rc).

#5

If I have got a variable self of type &mut Data, I consider borrowing already happened for self by the caller? Calling nested method via self.method() causes new borrowing (why?), although it is just passing a mut ref pointer to already borrowed and controlled by the caller data.


#6

Why does rust borrows self entirely for access of a field in mutate function? Self is already borrowed by the caller for the current function, is not it?


#7

A method is an opaque abstraction. Rust does not allow itself to see through its implementation and investigate your inner borrows. If it did, then modifying the implementation of a method in a way that adds borrows (currently) or possibly just shuffles them around (with NLL) would be a breaking change.