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.
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.
First of all, u
is not mutable at any point, as it was declared with let u
, not let mut u
. The reason why you can mutate the String
it points to is that it holds a mutable reference to it; make_ascii_uppercase()
modifies t
.
v
is also immutable (no mut
in let v
), so when you call show()
that works on immutable references, the borrowing rules are not violated - you can perform multiple immutable borrows at once.
This is confusing, so let's do some experiments.
Your code compiles:
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);
What happens if we change the let v
line to:
let v = &t;
error[E0502]: cannot borrow t
as immutable because it is also borrowed as mutable
--> :12:14
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 from u
, but the latter is trying to reborrow t
.
Let's try a different experiment. Putting the previous line back, but now adding something new:
let v = &*u; // the original reborrow
let w = u; // Try to move out of `u`
error[E0502]: cannot borrow t
as immutable because it is also borrowed as mutable
--> :12:14
Aha! That confirms that v
really is borrowing from u
rather than directly from t
.
Now, in the original, let's add an attempted mutation via u
to the end:
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);
u.make_ascii_uppercase();
Now I get:
error[E0502]: cannot borrow *u
as mutable because it is also borrowed as immutable
I think that basically explains what's going on:
u
borrows t
mutably. This stops t
being accessed at all directly.
v
borrows u
immutably. This means that u
can still be used immutably, but it can't be used mutably or moved out of.
- The other key thing is that you can only use mutable values if the full path to the item is mutable. Since
u
can't be borrowed mutably while v
exists, you can't use *u
mutably either. (This last bit is slightly handwavey; I'd welcome further clarifications...)