Potential Bug in borrow checker

I experienced a strange complaint from the borrow checker today and would like to know if this is intentional or a mistake. The code is the following:

#[derive(Default)]
pub struct Scope {
    pub parent: Option<Box<Scope>>,
    pub bindings: HashMap<Ident, Cell>,
}

impl std::ops::IndexMut<&Ident> for Scope {
    fn index_mut(&mut self, ident: &Ident) -> &mut Self::Output {
        if let Some(cell) = self.bindings.get_mut(ident) {
            return cell;
        }

        if let Some(parent) = &mut self.parent {
            return &mut parent[ident];
        }

        self.bindings.insert(ident.clone(), Cell::void());
        let Some(cell) = self.bindings.get_mut(ident) else {
            unreachable!()
        };
        cell
    }
}

There are some more impls, but i don't think they are relvant to this issue. The error message is the following:

error[E0499]: cannot borrow `self.bindings` as mutable more than once at a time
  --> runtime\src\scope.rs:34:9
   |
25 |     fn index_mut(&mut self, ident: &Ident) -> &mut Self::Output {
   |                  - let's call the lifetime of this reference `'1`
26 |         if let Some(cell) = self.bindings.get_mut(ident) {
   |                             ------------- first mutable borrow occurs here
27 |             return cell;
   |                    ---- returning this value requires that `self.bindings` is borrowed for `'1`
...
34 |         self.bindings.insert(ident.clone(), Cell::void());
   |         ^^^^^^^^^^^^^ second mutable borrow occurs here


Now i kind of get what it is saying, but I cannot come up with any possible scenario, where the previous borrow would not be dropped before going past the if.

My solution is:

impl std::ops::IndexMut<&Ident> for Scope {
    fn index_mut(&mut self, ident: &Ident) -> &mut Self::Output {
        if self.bindings.contains_key(ident) {
            let Some(cell) = self.bindings.get_mut(ident) else {
                unreachable!()
            };
            return cell;
        }

        if let Some(parent) = &mut self.parent {
            return &mut parent[ident];
        }

        self.bindings.insert(ident.clone(), Cell::void());
        let Some(cell) = self.bindings.get_mut(ident) else {
            unreachable!()
        };
        cell
    }
}

Which does not complain whatsoever. And in my mind that proves the point that it should work in the first place, unless one wants to argue that technically in the first case there is a None of type Option<&'1 mut Cell> in scope.

This is just problem case #3 from the NLL RFC. It will eventually be fixed by the Polonius borrow checker.

1 Like