Wrapping code in helper function verbatim leads to borrow error

92 Views Asked by At

I created a helper function get_node() for a common pattern, but for some reason it leads to borrow errors, but when copy pasting its contents it does not. I've marked the problematic lines with comments:

type NodeMap = HashMap<i32, Node>;

pub trait IdGenerator {
    fn gen(&mut self) -> i32;
}

pub struct Tree<'a> {
    active: i32,
    nodes: NodeMap,
    generator: &'a mut dyn IdGenerator,
}

impl<'a> Tree<'a> {
    pub fn create_sibling(&mut self) {
        let active = self.nodes.get(&self.active).unwrap(); // <-- THIS WORKS
        // let active = self.get_node(self.active);            // <-- THIS FAILS

        let mut node = Node::new(self.generator.gen(), active.parent);
        // [......]
    }

    fn get_node(&self, id: i32) -> &Node {
        self.nodes.get(&id).unwrap()
    }
}

Uncommenting and commenting the two let active... lines leads to the following error:

error[E0502]: cannot borrow `*self.generator` as mutable because it is also borrowed as immutable
  --> src/tree_new.rs:35:34
   |
34 |         let active = self.get_node(self.active);
   |                      ---- immutable borrow occurs here
35 |         let mut node = Node::new(self.generator.gen(), active.parent);
   |                                  ^^^^^^^^^^^^^^^^^^^^  ------------- immutable borrow later used here
   |                                  |
   |                                  mutable borrow occurs here

The error messages makes sense, but what I'm confused on is why the same borrowing logic doesn't apply to the original code without the helper function.

1

There are 1 best solutions below

0
On BEST ANSWER

This line immutably borrows self.nodes for the lifetime of active (which is the current block scope):

// immutably borrows self.nodes
let active = self.nodes.get(&self.active).unwrap();

// mutably borrows self.generator
self.generator.gen();

// no overlap, no problem ✔️

Later in the same scope you mutably borrow self.generator but self.nodes and self.generator don't overlap so there's no problems.

However, this line immutably borrows all of self, which includes self.generator, for the lifetime of active (which is the same block scope as before):

// immutably borrows self
let active = self.get_node(self.active);

// mutably borrows self.generator
self.generator.gen();

// compiler error:
// immutable borrow of self overlaps with mutable borrow of self.generator ❌

// ...

// this function signature says: as long as the returned
// reference is live the entire struct is immutably borrowed
fn get_node(&self, id: i32) -> &Node {
    self.nodes.get(&id).unwrap()
}

Now since all of self is immutably borrowed, when you try to mutably borrow self.generator later, the immutable borrow of self and the mutable borrow of self.generator overlap and you get a compiler error.

Unfortunately this is a known limitation of Rust. There's no way to "enrich" the type signature of your helper function to communicate to the Rust compiler that the immutable borrow depends specifically only on self.nodes and not on all of self. However there are people who are interested in adding this to the language and you can track their progress in the Partial Borrowing issue on the Rust RFCs repo. The typical workaround is to split borrows but your helper function is so tiny I don't think that approach applies here and your best bet would be to just get rid of the helper function and continue writing the logic inline.