The limitations that the compiler enforces are that mutable borrows must be exclusive and other usage of the borrowed thing cannot happen, even immutable borrows, while the mutable borrow exists. This is likely the reason why you are confused, as r1, r2, and r3 all point to the same string s
, and co-exists, but r1
should be exclusive, right!?
One must however not confuse the abstract, somewhat syntactical, and compile-time-only notion of “borrowing” with the question “what does the pointer point to”. In fact only r1
borrows s
. r2
however borrows r1
. More precisely, it borrows the target of r1
, often written *r1
, but this is a minor detail, and a good first approximation that works for many cases is to consider a borrow of r1
, so e.g. like a reference of type & &mut String
being created, which could then be de-referenced into a &String
reference as a second step.
How it comes about that r1
is borrowed is easy to explain: The coercion r1 as &String
is implemented as, i.e. equivalent to writing, the expression &*r1
, which – unsurprisingly – simply borrows *r1
.
let r2 = &*r1; // r2 borrows `*r1`
let r3 = r2; // r3 is a copy of r2
The compiler will consider r1
a new kind of owner, in a sense: it owns mutable access to the String
in question. The compile does not draw any connection between *r1
and the original string s
, so the code is significantly different from writing &s
instead of &*r1
, as far as the borrow-checker is concerned.
With the basic idea that r2
and r3
borrow from r1
, all that’s necessary to require the full code’s behavior is the knowledge that println!("{:p}", r1)
also only immutably borrows r1
, so this access to r1
does not conflict with the existence of r2
and r3
.
The minor difference that *r1
is borrowed, not r1
, has one effect in particular: If you borrow something, you usually cannot keep this borrow after the borrowed thing goes out of scope, but a re-borrow like r2 = &*r1
can exist for longer than the scope of the variable r1
. This is because nothing of importance to this re-borrow is actually held in the variable r1
itself, and the compiler offers some special reasoning that works as if you imagined that the original borrow of s
stored in r1
just lives on somewhere else, e.g. in your imagination, in the void, etc…
By the way, many cases of (implicit) re-borrows you are probably already familiar with, even though they might not strike you as all that remarkable in the first place. For example, looking at simple code like
struct S(…);
impl S {
fn f(&mut self) {…}
fn g(&self) {…}
fn h(&mut self) {
self.g();
self.f();
}
}
if you think about what happens in the implementation of h
for long enough, you will realize that there is an immutable &S
reference being passed to (and usable in) g
, but all the time the mutable self
reference in h
still keeps existing, as is evident by the fact that f
is still callable afterwards! This, too, exercises a re-borrow! The call desugars from self.g()
to S::g(&*self)
. In fact, the call to f
will also re-borrow, but mutably, desugaring to S::f(&mut *self)
. This is evident by the fact that the call self.f()
does not move self
, otherwise you couldn’t call it twice, as in:
fn h1(&mut self) {
self.g();
self.f();
self.f(); // works fine, too!
}
So reborrows are, interestingly enough: on first look a seemingly special and seemingly niche feature and one can go learning and using Rust without knowing about reborrowing for a relatively long time; on second thought they are something that can (mostly) be actually easily understood, since if borrows (i.e. references) are also understood of some sort of owned values that can be borrowed yet-again, a reborrow is just a “borrow of a borrow”; and finally, it turns out reborrows happen implicitly everywhere in Rust all the time, and without them, the most straightforward code examples wouldn’t even compile anymore 