This applies to shared reference being created independently of the &'long mut i32
. I.e. if there's a variable x: i32
, and you create let y = &mut x;
, then you cannot also create let z = &x
while the reference in y
is still alive.
On the other hand re-borrowing the &'long mut x
as a &'long x
is possible, even multiple times
fn f<'long>(x: &'long mut i32) {
let y: &'long i32 = &*x;
let z: &'long i32 = &*y;
println!("{}", y);
println!("{}", z);
}
furthermore, reborrowing a mutable reference can often happen implicitly
fn g<'long>(x: &'long mut i32) {
let y: &'long i32 = x;
let z: &'long i32 = y;
println!("{}", y);
println!("{}", z);
}
(playground)
The main problem with your original example
fn main () {
let mut i:i32 = 1;
let j = {
let x = &mut i;
let y = &x;
&**y
};
}
is that the variable x
only exists inside the block, hence the reference to x
cannot be alive after that block. So there's two parts to the problem:
- A reference to
x
is created, i.e. the &x
assigned to y
- This reference is used in a way that requires that reference to be alive for longer as the block in which
x
is in scope.
- This is due to
&**y
which turns y: &'short &'long mut i32
into &'short i32
, hence producing a reference whose lifetime is bound to the lifetime of that y = &x
reference.
Now your other two settings both drop one of these parts:
-
With
fn main () {
let mut i:i32 = 1;
let j = {
let x = &i;
let y = &x;
&**y
};
}
the second part of the problem is gone: Now &**y
turns y: &'short &'long i32
into &'long i32
. The lifetime of this reference is no longer connected to how long x
can be borrowed (from the expression &x
) but instead only connected to how long i
can be borrowed (from the expression &i
). IMPORTANT: Even though &i
is assigned to a short-lived variable x
, it can still contain a reference that lives longer than the scope of the variable it's assigned to! References can be moved around - shared references also copied - it doesn't really matter what kind of variable they are initially assigned to; the transformation &'short &'long i32
-> &'long i32
does simply copy the reference out of y
; it's basically a special case of dereferencing &'short T
-> T
where T: Copy
. The fact that you're writing &**y
instead of simply *y
is making this a bit less intuitive, but the compiler somehow understands that there's really no problem, i.e. that it's essentially the same as *y
.
-
With
fn main () {
let mut i:i32 = 1;
let j = {
let x = &mut i;
&*x
};
// let k=&i; // won't compile
j;
}
instead, the first part of the problem is gone. Here, we don't create any reference to a local varaible inside of the block in the first place. There's no "&x
" anywhere.
Edit:
In the last example above, it may be confusing that you're re-borrowing from a reference contained in x
for longer than the variable x
exists; but really for soundness all that matters for the re-borrowing is the lifetime argument of the type of the reference value that x
contains, not how long the containing variable exists.
Now, there's a problem: When x
goes out of scope, it gets dropped; dropping a value do something, so in general it would need access to what's inside. You cannot, for example, reborrow a reference inside of a Vec
for longer than that Vec
lives.
fn foo() {
let x = &mut 0_i32;
let z;
{
let mut y = vec![x];
z = &mut *y[0];
// y dropped
}
z; // doesn't compile
}
But for types that don't implement Drop
, like &mut T
, it could work. So e.g. for for fields in structs not implementing Drop
struct S<T>(T);
fn foo() {
let x = &mut 0_i32;
let z;
{
let y = S(x);
z = &mut *y.0;
// y dropped
}
z; // compiles fine
}
I don't have a source on that, but it seems to me that re-borrowing a mutable reference (almost) always works if moving that reference would also have worked. (This means it also works with Box
with has some special - somewhat magical - properties so you can move out of field of a struct behind a Box. So this works (link).) This also interacts nicely with the fact that Rust will quite often introduce implicit re-borrows of mutable references everywhere it might make sense; this would be a bad feature if it were able to make code no longer compile when the same code when it would move the reference would still compile.
E.g. on &mut T
method arguments, you'll have implicit re-borrows, as demonstrated by
fn foo() {
let x = &mut 0_i32;
bar(x); // x NOT moved
bar(x); // cann call again
}
fn bar(_: &mut i32) {}
This means the above is essentially the same as
fn foo() {
let x = &mut 0_i32;
bar(&mut *x);
bar(&mut *x);
}
fn bar(_: &mut i32) {}
But if you would do something like
fn foo() {
let mut i = 0;
let y;
{
let x = &mut i;
bar(x);
y = bar(x);
}
println!("{}", y);
}
fn bar(x: &mut i32) -> &mut i32 { x }
then the second call, y = bar(x)
is expected to move the&mut i
reference out of that block; the fact that it's equivalent to y = bar(&mut *x)
must not make the compilation fail.