Why can I assign a mutable reference to an immutable reference without violating the borrowing rules?

Feel free to also look into (especially the first part(s) of) this post of mine What is a good mental model of borrow checker? - #18 by steffahn, maybe it helps with further understanding, I don't know, but it already exists, so I can just link it ^^

The difference between s and *r1 is indeed (usually) non-existent at run-time, when borrowing either will create exactly the same pointer.[1] However, when borrowing *r1, you give the borrow checker the extra information that r1 should only be temporarily restricted (in much the same way in which s is restricted when it's borrowed, i.e. a mutable re-borrow of *r1 disallows any access to *r1 for the duration of that re-borrow, and an immutable re-borrow only disallows mutable access). On the other hand, directly borrowing s again will completely end every pre-existing borrow of s, so any later usage of r1 would be disallowed.

Regarding the question "why are these different", the first thing to note is that the re-borrowing case is less restrictive, so it's important that that operation works as intended when we want to use it, as temporarily restricting one borrow is a useful, and safe, operation, and it makes sense from the principle that types like & &mut String exist anyways! So we do definitely not want to turn any of today's cases of re-borrowing into error cases, when they can compile successfully just fine, while maintaining memory safety.

The reasonable follow-up question of “why not make borrowing s directly less restrictive in the same way then?” probably only has the answer that that's simply a lot harder to pull off (for the compiler): it would require more complexity in the borrow checker. There is no real strong, principled, reason I can think of why, especially a locally appearing in the same function, existing mutable borrow such as r1 in your example code couldn't implicitly be temporarily restricted by a new reference let r2 = &s; in generally the same way let r2 = &*r1; does, other than the reason “no one had proposed a full set of rules for such borrow-checking behavior yet”. Perhaps some people might argue though that it's also in a sense “less confusing”, especially for mutable re-borrows, when the target of a mutable reference like r1 can, while it exists, only ever be mutated by someone literally accessing “*r1”, and not indirectly by accessing the different variable s directly. On the other hand, there are cases where such behavior does seem rather desired, e. g. if you have a locally defined closure let f = || s.push_str("foo");, you might want to use such a closure for code-reuse purposes in use cases where you call pattern might look like f(); s.push_str("bar"); f() (expecting the result to be s == "foobarfoo") and more advanced borrow checking algorithm working along similar lines as the idea of making &s and &*r1 behave identically might be able to pull this off, too.


  1. There actually is a difference in the abstract “stacked borrows” model that the tool miri implements, in that borrowing s will result in the previously existing r1 to become entirely invalid, whereas borrowing from *r1 will not invalidate r1 but merely restrict its usability temporarily, until the borrow of *r1 ends. ↩︎

1 Like