Reborrowing of mutable reference
Asked Answered
S

1

20

When I wondered how a mutable reference could move into a method, all the questions began.

let a = &mut x;
a.somemethod(); // value of a should have moved
a.anothermethod(); // but it works.

I've googled a lot. (really a lot) And I've noticed that a mutable reference passed as a parameter to a function, always undergoes the following transformation. (which is called reborrowing)

fn test(&mut a) -> ();

let a = &mut x;
test(a); // what we write in code.
test(&mut *a); // the actual action in code.

So, I have googled more about "reborrowing", for its detail.

And this is what I've got.

In any of codes, x refers to an arbitrary data. I don't mention it because I don't think the type of it is important for discussion. (However, I used i32 in my own).

let a = &mut x;
let b = &mut *a; // a isn't available from now on
*a = blahblah; // error! no more access allowed for a
*b = blahblah; // I've wrote this code not to confuse you of the lifetime of b. If not mentioned, it always live till the scope ends.
let a = &mut x;
{
    let b = &*a;
    // *a = blahblah in this scope will throw an error, just like above case.
}
*a = blahblah; // but this works.

So alright. It's quite interesting. It seems like b borrows not only x but also a.

Perhaps, we can clarify reborrowing like this : &'a *(&'b mut x). It has borrowed x (which has a lifetime 'a in here), but also borrowed a (which has a lifetime 'b).

So I ran the following code to confirm my conjecture.

let x: i32 = 1; // I wanted to make it clear that x lives in this scope.
let b;
{
    let a = &mut x;
    b = &mut *a;
}
*b = 0;

But this works!

What?? But I just decided to get this. &'a mut *&mutx, not &'a mut *&'b mutx.

I had no idea why mut x is unavailable during the lifetime of &mut *&mutx nor why mut x is re-available after the lifetime of &mut *&mutx, but "okay, let's just say so".

But look at this. It's totally out of my mind for a clear and general understanding.

let x: i32 = 1;
let b;
{
    let a = &mut x;
    let b = &**&a;
} // error!! a should live longer than b!

Wasn't lifetime simply relying on what the real data b is referring to??? &'a **& &mut x, not &'a **&'b &'c mut x.

And now what??

&'a **&'b &mut x ??? (which was my guess).

How should I accept this complicated situation?

Synecdoche answered 28/12, 2020 at 7:19 Comment(1)
My expression of lifetime is totally nonsense. Sorry for my bad question. Just ignore it.Synecdoche
V
13

These are some great questions! I'll do my best to answer the ones I can.

The Rust Reference is a great place for finding answers to questions like this, about the deeper semantics of Rust.

First, for your question about method resolution, the Reference says:

When looking up a method call, the receiver may be automatically dereferenced or borrowed in order to call a method. This requires a more complex lookup process than for other functions, since there may be a number of possible methods to call. The following procedure is used:

The first step is to build a list of candidate receiver types. Obtain these by repeatedly dereferencing the receiver expression's type, adding each type encountered to the list, then finally attempting an unsized coercion at the end, and adding the result type if that is successful. Then, for each candidate T, add &T and &mut T to the list immediately after T.

For instance, if the receiver has type Box<[i32;2]>, then the candidate types will be Box<[i32;2]>, &Box<[i32;2]>, &mut Box<[i32;2]>, [i32; 2] (by dereferencing), &[i32; 2], &mut [i32; 2], [i32] (by unsized coercion), &[i32], and finally &mut [i32].

The link above goes into more detail.

For the rest of your questions, I think this is mostly about type coercion. Some relevant coercions are:

  • &mut T to &T
  • &T or &mut T to &U if T implements Deref<Target = U>
  • &mut T to &mut U if T implements DerefMut<Target = U>

Notably, &U and &mut U both implement Deref<Target = U>, and &mut U also implements DerefMut<Target = U>. Therefore, the second/third rules lead to the following coercions:

Let T be: Coerce from To
&U &&U &U
&U &mut &U &U
&mut U &&mut U &U
&mut U &mut &mut U &mut U

Now with the reference out of the way, let's look at your questions in more detail:

let a = &mut x;
let b = &mut *a; // a isn't available from now on
*a = blahblah; // error! no more access allowed for a
*b = blahblah; // I've wrote this code not to confuse you of the lifetime of b. If not mentioned, it always live till the scope ends.

In order to write into a reference, it clearly must be a mutable (aka unique) reference. When you write let a = &mut x, now a is the only way to access x. Now when you write let b = &mut *a, it essentially means let b = &mut *(&mut x), which simplifies to let b = &mut x.

Here, b mutably borrows a, Rust will not let you use a until b is destroyed... or so it seems for now.

let a = &mut x;
{
    let b = &*a;
    // *a = blahblah in this scope will throw an error, just like above case.
}
*a = blahblah; // but this works.

Here, let b = &*a turns into let b = &*(&mut x), so let b = &x. No matter what kind of reference b is, a is a mutable reference and must be unique, so you can't use it until b is gone (out of scope).

let mut x: i32 = 1; // I wanted to make it clear that x lives in this scope.
let b;
{
    let a = &mut x;
    b = &mut *a;
}
*b = 0;

(I assume you meant let mut x).

Now this is where it gets interesting. Here, since a and b both mutably point to the same object, Rust will not let you use a until b is destroyed. It seems like the reasoning should be, "since b mutably borrows a", but that's actually not the case. Normally, b would in fact borrow a, and this is the case for smart pointers like Box, std::cell::RefMut, etc. However, references get special treatment: Rust can analyze the memory they point to.

So when you write let b = &mut *a, b doesn't actually borrow a! It only borrows the data a points to. This distinction wasn't relevant before, but here it is what allows the code to compile.

As for your last example, I can't get it to break in the way you described.

Valida answered 28/12, 2020 at 21:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.