Reputation: 1071
I have found a case where manually inlining a function changes the way the borrow-checker treats it, such that it no longer compiles. Presumably it is relying on the information in the function signature. How can I provide this information in the inlined version?
Let 'a
and 'b
be lifetimes with 'a
shorter than 'b
(which can be written 'b: 'a
).
Suppose I have a p: &'b mut f32
. I can borrow p
briefly (with &mut p
) to obtain q: &'a mut &'b mut f32
.
&'a mut &'b mut f32
is equivalent to &'a mut &'a mut f32
because 'b: 'a
?I can then dereference q
(with *q
) to obtain r: &'a mut f32
. I can write to the f32
via r
(with *r = something
), and I can later (outside lifetime 'a
) read back the value via p
(with *p
).
Here is some working code that I think uses the above sequence:
fn reborrow<'a, 'b: 'a>(q: &'a mut &'b mut f32) -> &'a mut f32 {
*q
}
fn main() {
let mut x: f32 = 3.142;
let mut p = &mut x;
{
let q = &mut p;
let r = reborrow(q);
*r = 2.718;
}
assert_eq!(*p, 2.718);
}
(Replacing *q
with q
in the body of reborrow()
also works, because Rust inserts the necessary dereference if it is missing).
If I manually inline the reborrow()
call, it no longer compiles:
fn main() {
let mut x: f32 = 3.142;
let mut p = &mut x;
{
let q = &mut p;
let r = *q; <-- ERROR REPORTED HERE.
*r = 2.718;
}
assert_eq!(*p, 2.718);
}
error[E0507]: cannot move out of borrowed content
Who took away my toys? What is the type inference thinking/missing?
Can I annotate the let
binding somehow to make the compiler infer the same types as in the previous version?
Here's another version that works, but which doesn't define the name r
:
fn main() {
let mut x: f32 = 3.142;
let mut p = &mut x;
{
let q = &mut p;
**q = 2.718;
}
assert_eq!(*p, 2.718);
}
Here's a work-around that defines the name r
and works, but does not use the same sequence of borrows and dereferences:
fn main() {
let mut x: f32 = 3.142;
let mut p = &mut x;
{
let q = &mut p;
let r = &mut **q;
*r = 2.718;
}
assert_eq!(*p, 2.718);
}
I made a playground combining all four versions.
Upvotes: 12
Views: 3177
Reputation: 299730
The obvious solution works, as one could expect:
fn main() {
let mut x: f32 = 3.142;
let mut p = &mut x;
{
let r: &mut f32 = p;
*r = 2.718;
}
assert_eq!(*p, 2.718);
}
It seems relatively intuitive and is what I would expect a newcomer to end up with.
If you start thinking about it, however, it will not make sense. As described, it looks like:
let r: &mut f32 = p;
moves out of p
p
later in assert_eq!(*p, 2.718);
A reasonable explanation would be that p
is Copy
, however it's not1!
The answer is that, implicitly, Rust is performing a re-borrowing behind the scenes. That is, the explicit code is:
fn main() {
let mut x: f32 = 3.142;
let mut p = &mut x;
{
let r: &mut f32 = &mut *p;
*r = 2.718;
}
assert_eq!(*p, 2.718);
}
We can check this by attempting to read p
after re-borrowing it, and check the compiler error:
error[E0502]: cannot borrow `p` as immutable because `*p` is also borrowed as mutable --> <anon>:6:24 | 5 | let r: &mut f32 = p; | - mutable borrow occurs here 6 | println!("{}", p); | ^ immutable borrow occurs here 7 | *r = 2.718; 8 | } | - mutable borrow ends here error: aborting due to previous error
Which confirms that p
is indeed only borrowed mutably, and not moved, cloned or copied.
1 A mutable reference cannot be Copy
or even Clone
as it would violate the Aliasing XOR Mutability principle which underpins Rust safety.
Upvotes: 14
Reputation: 430368
I can't possibly begin to explain this, but you can do a similar trick as the implicit dereference and say that r
is &mut f32
:
fn main() {
let mut x: f32 = 3.142;
let mut p = &mut x;
{
let q = &mut p;
let r: &mut f32 = q;
*r = 2.718;
}
assert_eq!(*p, 2.718);
}
Upvotes: 3