A mutable reference can be borrowed immutably, however this is not what is happening here.
When forming a reference with &
, you need to be explicit about mutability; unless you specify &mut
it will be an immutable reference.
Your example can be reduced to:
use std::ascii::AsciiExt;
fn main() {
let mut t = "s".to_string();
let u = &mut t;
u.make_ascii_uppercase();
let v = &*u;
let () = v;
}
The last line is a trick to get the compiler to tell us (in the error message) what the type of v
is. It reports:
error[E0308]: mismatched types
--> <anon>:9:9
|
9 | let () = v;
| ^^ expected reference, found ()
|
= note: expected type `&std::string::String`
= note: found type `()`
Here we have:
u
: an immutable binding, which is a mutable borrow of t
v
: an immutable binding, which is an immutable re-borrow of t
through u
If, however, I change the v
line to let v = &mut *u;
, then I get expected type '&mut std::string::String'
and then we have:
u
: an immutable binding, which is a mutable borrow of t
v
: an immutable binding, which is a mutable re-borrow of t
through u
The important concept here is re-borrowing, which is what &*u
and &mut *u
are about. Re-borrowing allows forming a new reference from an existing reference:
- a re-borrow access the initially borrowed variable
- for the lifetime of the re-borrow, the reference from which it is formed is borrowed
The re-borrowing rules are relatively simple, they mirror the borrowing rules:
- if you start from an immutable reference:
- you can re-borrow it only as an immutable reference, with multiple concurrent immutable re-borrow if you wish
- if you start from a mutable reference:
- you can either re-borrow it as a mutable reference, exclusively
- or you can re-borrow it as an immutable reference, with multiple concurrent immutable re-borrow if you wish
It is interesting to note that a re-borrowed reference can live longer than the reference it was formed from:
fn main() {
let mut t = "s".to_string();
let v;
{
let u = &mut t;
v = &mut *u;
}
v.make_ascii_uppercase();
show(v);
}
This is necessary to ensure that you can return a reference from functions; of course.
So, ultimately, a re-borrow is tracked down to the original borrowed value by the compiler; however, due the re-borrowing mechanics it allows forming an immutable reference to this original value even though a mutable reference is in scope... and simply make sure that this mutable reference is unusable for the lifetime of the new immutable reference.
When a function takes a reference, the compiler automatically introduces a re-borrow at the call site with the appropriate mutability; this is what happens with show
here: show(u)
really is show(&*u)
with a new immutable reference formed for the duration of the function call.