It seems that u
, a mutable borrow, becomes automatically immutable in
let v = &*u;
Both u
and v
are then immutable borrowed references so they are both allowed.
use std::ascii::AsciiExt;
fn show(a: &str) {
println!("a={}", a);
}
fn main() {
let mut t = String::new();
t.push('s');
let u = &mut t;
u.make_ascii_uppercase(); // u is really mutable here
let v = &*u; // u became immutable to allow this?
show(u); // both u and v are now accessible!
show(v);
}
Outputs:
a=S
a=S
If I try to use u
as a mutable borrow after
show(v);
compiler will recall that
let v = &*u;
is really not allowed:
cannot borrow `*u` as mutable because it is also borrowed as immutable
Is it a bug or is there really some "automatically convert mutable borrow to immutable when mutability is no longer needed" principle? I am using Rust 1.13.0.
First of all,
u
is not mutable at any point, as it was declared withlet u
, notlet mut u
. The reason why you can mutate theString
it points to is that it holds a mutable reference to it;make_ascii_uppercase()
modifiest
.v
is also immutable (nomut
inlet v
), so when you callshow()
that works on immutable references, the borrowing rules are not violated - you can perform multiple immutable borrows at once.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:
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:Here we have:
u
: an immutable binding, which is a mutable borrow oft
v
: an immutable binding, which is an immutable re-borrow oft
throughu
If, however, I change the
v
line tolet v = &mut *u;
, then I getexpected type '&mut std::string::String'
and then we have:u
: an immutable binding, which is a mutable borrow oft
v
: an immutable binding, which is a mutable re-borrow oft
throughu
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:The re-borrowing rules are relatively simple, they mirror the borrowing rules:
It is interesting to note that a re-borrowed reference can live longer than the reference it was formed from:
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 isshow(&*u)
with a new immutable reference formed for the duration of the function call.This is confusing, so let's do some experiments.
Your code compiles:
What happens if we change the
let v
line to:Ok, so that's different. That tells me that
&*u
, despite being the same type as&t
, is not the same; the former is (sub-)borrowing fromu
, but the latter is trying to reborrowt
.Let's try a different experiment. Putting the previous line back, but now adding something new:
Aha! That confirms that
v
really is borrowing fromu
rather than directly fromt
.Now, in the original, let's add an attempted mutation via
u
to the end:Now I get:
I think that basically explains what's going on:
u
borrowst
mutably. This stopst
being accessed at all directly.v
borrowsu
immutably. This means thatu
can still be used immutably, but it can't be used mutably or moved out of.u
can't be borrowed mutably whilev
exists, you can't use*u
mutably either. (This last bit is slightly handwavey; I'd welcome further clarifications...)