Question about `move semantic` on `&mut T` and borrow checker

The code can't be compiled just as expected:

fn main() {
    type Arr = [i32; 3];
    fn get_ref(r: &mut Arr) {}

    let mut data = [1, 2, 3];
    let data_ref = &mut data;
    let data_ref_1 = data_ref;

    get_ref(data_ref);
    println!("{:?}", data_ref);
}

Compile the above code get the error:

error[E0382]: borrow of moved value: `data_ref`
--> src/main.rs:9:17
|
6 |         let data_ref = &mut data;
|             -------- move occurs because `data_ref` has type `&mut [i32; 3]`, which does not implement the `Copy` trait
7 |         let data_ref_1 = data_ref;
|                          -------- value moved here
8 | 
9 |         get_ref(data_ref);
|                 ^^^^^^^^ value borrowed here after move

error: aborting due to previous error

But, if I add the data_ref_1 type announcement, This code can be compiled successfully:

fn main() {
    type Arr = [i32; 3];
    fn get_ref(r: &mut Arr) {}

    let mut data = [1, 2, 3];
    let data_ref = &mut data;
    let data_ref_1: &mut Arr = data_ref;

    get_ref(data_ref);
    println!("{:?}", data_ref);
}

Since variable data_ref is a mutable reference, when it was binded to data_ref_1, it should move to data_ref_1 .

So why the second code can be compiled successfully? Is that affected by NLL?

That's beyond my cognition.

This is a special coercion (I don't think it has a name1). If you are trying to borrow a unique reference (&mut T), then it will get reborrowed to allow this exact pattern. Note: coercions only apply when the types are already constrained, so it doesn't apply when you infer the type of data_ref_1, but as soon as you add a type annotation, you have constrained the types enough for it to work.

Note: this works too

fn main() {
    type Arr = [i32; 3];
    fn get_ref(r: &mut Arr) {}

    let mut data = [1, 2, 3];
    let data_ref = &mut data;
    let data_ref_1: &mut _ = data_ref;

    get_ref(data_ref);
    println!("{:?}", data_ref);
}

1 This isn't deref coercion, it's a special coercion that only applies to references. It's probably similar to unsizing coercion based on this discussion

"Implicit reborrow" is the only term I am aware of.

It does happen at coercion sites, but I don't think it's closely related to (other types of) coercions. It's just a convenience so you don't have to write &mut *data_ref quite as often.

There seems to have been quite a rash of reborrowing-related questions lately, for instance:

5 Likes

@trentj that's exactly the term I was looking for (how did I forget that! :frowning:)

This is a bit weird to me. Now we have two mutable references to data, which I thought isn't allowed?.

I can actually mutate the data through both ref and ref_1.

    let mut _data = 1;
    type_of("_data", &_data);

    let _data_ref = &mut _data;
    type_of("_data_ref", &_data_ref);

    let _data_ref_1 : &mut _ = _data_ref;
    type_of("_data_ref_1", &_data_ref_1);


    *_data_ref_1 = 30;    // mutating _data via ref_1
    println!("{}", _data_ref);

    *_data_ref = 40;  // mutating data via ref
    println!("{}", _data);
_data: is type i32
_data_ref: is type &mut i32
_data_ref_1: is type &mut i32
30
40

You can, but only in that inside-out order. One is borrowing from the other, and non-lexical lifetimes will let that lifetime expire after the last inner use, so the outer can access it again.

wait, that looks like how mutable reference parameters are declared in function. Is that the same mechanism?

Is there any usage of this beside being confusing?

Search this forum for the term "reborrowing".

What looks like what? I'm not sure I understand. If you mean that assigning something to a variable declared as &mut _ works the same way as passing something to a function taking a &mut _ argument, yes, both are implicit reborrowing.

@trentj @RustyYato Thank you both for the answer and the detail explain! "implict reborrow", I'll search for that~

Besides, Is there any formal docs (like rfc or reference) that describe the Implicit reborrow?

It's briefly described in RFC 114.

Also, this rejected RFC to extend reborrowing to other types attempted to define it a bit more, and this issue has some related discussion.

2 Likes

I think it is worth pointing out that reborrowing acts just like any other kind of mutable borrowing with the lifetime checker — it's just that it automatically happens in some places you might be surprised about.

Mutably borrowing a value will give you a mutable reference into somewhere in that value, and marks the value as unusable in the region described by the lifetime marker on the resulting mutable reference. It's just that the value that is marked unusable here is itself a mutable reference.

In the example before, the _data_ref_1 variable was created by mutably borrowing _data_ref, which meant that _data_ref was unusable while _data_ref_1 was still in use. Just like if _data_ref had been e.g. an integer and we took a mutable reference to the integer. Once _data_ref_1 went away, _data_ref is no longer mutably borrowed, and now usable again like normal.

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.