[Solved] Do explicit returns affect the scope of (mutable) borrows?

I know I can't mutably borrow more than once at a time. I'm trying to structure the scopes in my program such that each borrow ends before the next begins. However, using an explicit return seems to throw off the scoping rules so that I can't do the borrows the way I want.

Here's an example of what I mean. Ignore the trait definition, the content is in the body of the function.

trait BTreeMapExt<K, V> where K: Ord, V: Default {
    fn get_mut_default(&mut self, key: K) -> &mut V;
}

impl<K, V> BTreeMapExt<K, V> for BTreeMap<K, V> where K: Ord, V: Default {
    fn get_mut_default(&mut self, key: K) -> &mut V {
        {
            let x = self.get_mut(&key);
            match x {
                Some(value) => return value, // Error [E0499]: cannot borrow `*self` as mutable more than once at a time
                None => (),
            };
        }
        {
            let y = self.insert(key, V::default());
        }
        unimplemented!();
    }
}

If you replace the return value with any other expression (e.g. ()), then everything works fine. The nested scopes are sufficient to keep the mutable borrows from interfering.

There must clearly be something about the borrow checker I'm not understanding here. Perhaps the use of return causes the borrow scope to run until the end of the function? Otherwise I can't make sense of this.

P.S. In case you're wondering what I'm trying to do here: I'm trying to define an operator that works like operator[] in C++'s std::map class, i.e. create the element with a default value if it doesn't exist and return a reference to it either way. I'm not sure how to even define this if the pattern above doesn't work. The code I would have wanted to write is below.

trait BTreeMapExt<K, V> where K: Ord, V: Default {
    fn get_mut_default(&mut self, key: K) -> &mut V;
}

impl<K, V> BTreeMapExt<K, V> for BTreeMap<K, V> where K: Ord, V: Default {
    fn get_mut_default(&mut self, key: K) -> &mut V {
        match self.get_mut(&key) {
            Some(value) => return value,
            _ => () // Fall through: the key does not exist
        }
        match self.insert(key, V::default()) {
            Some(_) => unreachable!(), // Can't happen, we just checked the key
            _ => (), // Fall through: the insertion succeeded
        }
        match self.get_mut(&key) {
            Some(value) => value,
            _ => unreachable!(), // Can't happen, we just inserted the key
        }
    }
}
1 Like

I think the problem is that the lifetime (i.e. scope of a borrow) can’t be conditional.

If you elaborate the type of get_mut_default (using the lifetime elision rules), you get:

fn get_mut_default<'a>(&'a mut self, key: K) -> &'a mut V

This says the lifetime of the self reference must be equal to that of the returned reference. If we propagate that knowledge through the match clause, the compiler would infer these lifetimes for x and value:

let x: Option<&'a mut V> = self.get_mut(&key);
match x {
    Some(value: &'a mut V) => return value,
    None => (),
};

Since mutable references are exclusive, once self has been used once, self can’t be used again. That’s why the compiler complains about self.insert.

Intuitively it seems as though you should be able to access self again, but as far as the compiler is concerned, self was forfeit the moment it went into .get_mut. In this regard, it is much like how if you pass String into a function and that function fails to accomplish its task and returns None, your String is gone – you can’t get it back!

You might ask: “But how come it works if I omit the return value?”

If you omit the return value, then the constraint that the lifetime of value is equal to the lifetime of self is lost. This means instead of the above, the compiler would infer a shorter lifetime for x and value:

{'_b
    let x: Option<&'_b mut V> = self.get_mut(&key); // <--- reborrowing
    match x {
        Some(value: &'_b mut V) => return value,
        None => (),
    };
}

The compiler does this through reborrowing, which borrows the &'a mut Self with a shorter duration '_b to obtain &'_b mut Self. Unlike the previous case where &'a mut Self is entirely unavailable for the remainder of the function, in this case &'a mut Self is only temporarily unavailable. Once the scope '_b ends, &'a mut Self is made available again.

As for a workaround, perhaps try the Entry API?

2 Likes

Baby Steps - case 3 there is essentially the same thing as the topic of this thread.

1 Like

Thanks for the explanations, that makes sense. (And the Entry API does indeed look like a much cleaner way of doing what I want to do.)