[blog post] Nested method calls via two-phase borrowing

After discussing with @RalfJung, I found the perfect example of why we cannot just unsafe that it is safe to have shared access to the &mut borrow simply because we got back a shared borrow. Consider the existing method get_mut defined on Mutex:

fn get_mut(&mut self) -> LockResult<&mut T>

The effect of this method is to bypass the lock and grant mutable access to the contents. One could easily write a wrapper for this method like so:

fn access<T>(m: &mut Mutex<T>) -> &T {
    m.get_mut().unwrap()
}

Not that this function has the signature pattern we're looking for: it takes in an &'a mut T and returns an &'a T. It does not acquire any locks, it simply gives us (shared) access to the contents of the mutex. This function is safe today, because we had unique access to the mutex for 'a and we surrendered that access in order to gain (shared) access to its contents.

However, if we were to adopt this idea that taking in an &mut T and returning an &T implicitly "demotes" that &mut borrow to a shared borrow, that would imply that we could go on using the mutex afterwards:

let mut mutex = Mutex::new(22);
let contents0: &usize = access(&mut mutex);

let guard = mutex.lock().unwrap(); // illegal today, legal under this demotion idea
let contents1: &mut usize = &mut *guard;

Now we have a shared borrow (contents0) and a mutable borrow (contents1) pointing at the same memory. Not good!

So, clearly -- given that existing libstd APIs rely on it -- we need some form of explicit declaration if we want to support this pattern. I imagine that if we (eventually) adopted some kind of type that let you take in an &mut which had two associated lifetimes: one, the mutable range, and the other, a longer, shared range, then we could express signature that way. It'd be something like:

fn mut_then_share<'w, 'r: 'w>(&{'w -> 'r} mut self) -> &'r u32

Here I am writing &{'w -> 'r} mut self to mean "mutable for 'w, then shared for 'r".

I am not a priori against such an extension, but I would want to pursue it after we finish up with the NLL work. It doesn't seem a priori related to me. Moreover, I think there is no particular reason that we would have to solve the nested method call problem using the same concept (that is, it is ok to have an &mut borrow not start immediately -- or to change the desugaring -- to address the problem of the &mut starting too soon, while having also the ability to, up-front, borrow something for a period 'w that degrades into a shared borrow after that).

4 Likes