Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I reborrow a mutable reference without passing it to a function?

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?

How I think it's working

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.

  1. Have I correctly understood that &'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).

With a function call

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).

Manually inlined

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
  1. Who took away my toys? What is the type inference thinking/missing?

  2. Can I annotate the let binding somehow to make the compiler infer the same types as in the previous version?

Some other attempts

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.

like image 206
apt1002 Avatar asked Mar 27 '17 00:03

apt1002


People also ask

What is an immutable borrow in Rust?

With Immutable borrow, the variable borrowing can read the value but cannot mutate (even if the original value is mutable) With immutable borrow, the borrowing variable is guaranteed that the value would not change. Any code that tries to violates these conditions would lead to a compile-time error.

What is mutable reference?

A mutable reference is a borrow to any type mut T , allowing mutation of T through that reference. The below code illustrates the example of a mutable variable and then mutating its value through a mutable reference ref_i .

Why might we choose to borrow a value in Rust?

Rust Reference and Borrow Value Since a value can only have an owner at a given time, using it inside the function will transfer ownership. Since we don't want the ownership to be transferred to the function, we borrow the value using by referencing its owner.

What is the Rust borrow checker?

The borrow check is Rust's "secret sauce" – it is tasked with enforcing a number of properties: That all variables are initialized before they are used. That you can't move the same value twice. That you can't move a value while it is borrowed.


Video Answer


2 Answers

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
  • and yet we use 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.

1A mutable reference cannot be Copy or even Clone as it would violate the Aliasing XOR Mutability principle which underpins Rust safety.

like image 124
Matthieu M. Avatar answered Oct 21 '22 11:10

Matthieu M.


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);
}
like image 40
Shepmaster Avatar answered Oct 21 '22 13:10

Shepmaster