Why does the type annotation incur the reborrow?

Given the code

    let mut a = String::from("test");
    let b = & mut a;
    let c = b;  // #1 b is moved here 
    println!("{b}");  // #2 error 

Since & mut T does not implement Copy trait, b is moved to c at #1 and cannot be used at #2, the compiler emit the expected error Rust Playground

However, if we add the type annotation for variable c, the case will become weird

    let mut a = String::from("test");
    let b = & mut a;
    let c: & mut String = b;  // #3
    println!("{b}");   // #4

The use of b at #4 is ok. Rust Playground The only possible reason I supposed is that the reborrow mechanism occurs at #3. Is this the recorded context for reborrowing?

4 Likes

AFAIK, reborrowing won't be formally documented until rustc adopts Chalk.

Now UCG might describe it in some way.

It's similiar to the difference between these two:

// Fails
fn foo<'a>(b: &'a mut i32) {
    let c: &'a mut i32 = b;
    *b = 1;
}

// Ok
fn foo2<'a>(b: &'a mut i32) {
    let c: &mut i32 = b;
    *b = 1;
}

&mut T is not the full name of a type, let c: &mut String equals to let c: &'_ mut String, which could have a different lifetime of b.

The question seems to be "why reborrow triggered only in second case, not in the first?" The current rule of thumb is simple - "reborrow before inference". That is, all reborrows are inserted when the type is already known; if it has to be inferred - it will be inferred with lifetime and won't be subject to reborrowing rules.

2 Likes

So, this is just a compiler black box now?

I think the second case is ok since the lifetime of the c could be a shorter lifetime than 'a such that c can be ended according to NLL.

On mobile and can't play around to confirm this, but I suspect it boils down to let without ascrption is not a coercion site.

I'm not sure whether reborrowing counts as coercion, however - otherwise it wouldn't help with multiple consecutive mutable borrows, since the original borrow would still be moved (and only then coerced).

1 Like

I was thinking of the first listed coercion, which I've seen cited as reborrow "documentation" (even though it mentions nothing about being able to produce a new &mut and so on, i.e. being embarrassing inadequate to call documentation).

But if you still don't buy it as a coercion (and I don't know that I'd disagree), my previous post can be rephrased as "I'm unaware offhand of any reborrow site that's not also a coercion site (and vice-versa)".

(I still haven't taken the time to play around to poke at that hypothesis though.)

Reborrowing was an important coercion before Rust 1.0

Update: that article also proposed[1]:

Interactions with inference

One interesting aspect of coercion is its interaction with inference. For coercion rules to make sense, we need to know all the types involved. But in some cases we are in the process of inferring the types, and the decision of whether or not to coerce would in fact affect the results of that inference. In such cases we currently do not coerce.

which agrees with the rule of thumb mentioned by @Cerber-Ursi .


  1. though things like ~T, autoborrowed function arguments and silent mutability don't exist today. ↩ī¸Ž

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.