Mutable reborrow sharing


#1

I am not sure what to call this, the title is my provisional name. I think this is a reasonable feature request to borrow checker, because workaround seems mechanical to me.

Given these definitions:

struct Field;
impl Field {
    fn mutate(&mut self) {}
}
struct Context {
    source: Field,
    destination: Field,
}
struct Packet {
    context: Context,
}

This function compiles:

fn process_packet(packet: &mut Packet) {
    let source = &mut packet.context.source;
    let destination = &mut packet.context.destination;
    source.mutate();
    destination.mutate();
}

But as soon as you box Context, it doesn’t compile any more.

struct Packet {
    context: Box<Context>,
}
error[E0499]: cannot borrow `packet.context` (via `packet.context.destination`) as mutable more than once at a time
  --> test.rs:18:28
   |
17 |     let source = &mut packet.context.source;
   |                       --------------------- first mutable borrow occurs here
18 |     let destination = &mut packet.context.destination;
   |                            ^^^^^^^^^^^^^^^^^^^^^^^^^^ second mutable borrow occurs here

This can confuse you so that you think this can’t be done. But since the following rewritten function compiles, I think rustc just should accept the code.

fn process_packet(packet: &mut Packet) {
    let context = &mut *packet.context;
    let source = &mut context.source;
    let destination = &mut context.destination;
    source.mutate();
    destination.mutate();
}

#2

Does rustc know that Box::deref_mut is so transparent? Hypothetically, what if that implementation mutated its contents somehow before returning the reference? Your code with the error is calling deref_mut twice, whereas the explicit context borrow is making sure to call deref_mut just once.


#3

Right, strictly speaking, the compiler can’t do the rewrite I did without assuming things about Box. (On the other hand, Box is already a languae item, so the compiler could make assumptions.)

But even if we assume arbitrary Box implementation, it is still the case that source and destination can’t possibly alias. If struct type T has fields f1, f2, and expression e1, e2 is of type T, &mut e1.f1 and &mut e2.f2 do not alias. Rewrite was one way to demonstrate this.

This function compiles, with or without box:

fn process_packet(packet: &mut Packet) {
    packet.context.source.mutate(); // 1
    packet.context.destination.mutate(); // 2
}

There are four borrows here. packet.context and packet.context.source borrowed for statement 1, and packet.context and packet.context.destination borrowed for statement 2.

The reason the code doesn’t compile with let assignment is that let extends borrow from statement to block. It seems to me that both packet.context and packet.context.source borrows are extended, but only extending packet.context.source borrow (because that is what is assigned) and keeping packet.context borrow for statement would allow the code to compile. Or is this wrong way to think about things?


#4

If you allow an arbitrary Box, then you can’t make any assumptions about what deref_mut is doing. According to the function signature, it has free mutable reign over its entire self. That’s the aliasing – it could completely clobber the previous source which you still have borrowed.


#5

Oops, yes you are right. Mutable borrow of packet.context in the second statement conflicts with mutable borrow of packet.context.source assigned to source.

Thanks.