Difference between implicit and explicit variable declaration

fn main() {
    let mut vec0 = vec![22, 44, 66];

    let vec1:&mut Vec<i32> = &mut vec0;
    let vec2:&mut Vec<i32> = vec1;

    println!("{:?}", vec2);
    println!("{:?}", vec1);
}

This works

fn main() {
    let mut vec0 = vec![22, 44, 66];

    let vec1:&mut Vec<i32> = &mut vec0;
    let vec2 = vec1;

    println!("{:?}", vec2);
    println!("{:?}", vec1);
}

But this not works.

error[E0382]: borrow of moved value: vec1
--> src\main.rs:8:22
|
4 | let vec1:&mut Vec = &mut vec0;
| ---- move occurs because vec1 has type &mut Vec<i32>, which does not implement the Copy trait
5 | let vec2 = vec1;
| ---- value moved here
...
8 | println!("{:?}", vec1);
| ^^^^ value borrowed here after move
|
= note: this error originates in the macro $crate::format_args_nl which comes from the expansion of the macro println (in Nightly builds, run with -Z macro-backtrace for more info)

Why vec2 declaration in first code is borrow but in second code is move? I thought they are same.

A mutable reference can't be duplicated. That would violate sharing-xor-mutability, the fundamental principle of the Rust memory model.

Thus, if you assign a value of type mutable reference to a new place, it is moved and you can't access the original again.

Because accessing a mutable reference many times in a row is a commonly wanted thing, the compiler implicitly reborrows in some situations; ie., dereferences the mutable reference, and then immediately takes a temporary reference to the resulting place. [1]

This implicit reborrowing mechanism only kicks in in some special cases, for example in function arguments and when the assigned variable has an explicit and concretely reference type (ie., not a generic "all shapes admitted" T). This makes sure you want a reborrow and that you have to opt in – otherwise, it would be impossible to "just" move a mutable reference like any other value.


Incindentally, I think that this implicit reborrowing can hide an important technical detail, and therefore I almost never rely on it. In these situations, I will try very hard to spell out the reborrows explicitly:

let vec2 = &mut *vec1;

This also gets rid of the redundant type annotation and is slightly easier to read, IMO.


  1. This doesn't "duplicate" the mutable reference in the sense that there still aren't two overlapping independent copies. The lifetime of the reborrowed "copy" is tied to (and strictly shorter than) that of the original, so the compiler can keep track of when it is invalidated, and prevents you from using the original while the reborrow is in scope. Thus, no shared mutability occurs, and this is sound. ↩ī¸Ž

8 Likes

Or to put it quite tersely, the explicit annotation makes the variable declaration a coercion site.

https://doc.rust-lang.org/reference/type-coercions.html#coercion-sites

4 Likes

Apparently that list is incomplete, since this also works:

fn main() {
    let mut vec0 = vec![22, 44, 66];

    let vec1:&mut Vec<i32> = &mut vec0;
    let vec2:&mut Vec<i32>;
    vec2 = vec1;  // <- coercion site?

    println!("{:?}", vec2);
    println!("{:?}", vec1);
}

I'm sure that is included:

let statements where an explicit type is given

In your example, the marked line is an initialization, not an assignment, since vec2 is uninitialized in its declaration. Maybe the wording could be made more precise by saying that it's not syntactically the let statement that matters, but the fact that the LHS is explicitly typed, whether or not it's on the same line as the initialization.

1 Like

So you're saying that the definition of coercion sites should be expanded to include all initializations, not only those that are part of let statements.

That would also not be sufficient. The following works:

fn main() {
    let mut vec0 = vec![22, 44, 66];

    let vec1:&mut Vec<i32> = &mut vec0;
    let mut vec2:&mut Vec<i32> = vec1;
    
    // Not an initialization. Coercion still happens.
    vec2 = vec1;

    println!("{:?}", vec2);
    println!("{:?}", vec1);
}

Edit: I think the real answer is that reborrowing is not a coercion? Rather, it has to do with subtyping: &'b mut Vec<i32> is a subtype of &'a mut Vec<i32>. It seems that reborrowing is just copying, &mut references can be copied, it's just that the borrow checker intervenes in certain cases.

The reference explicitly mentions that this is not the case:

So no, reborrowing is not just copying. Copying and cloning both create independent and exact duplicates (Clone::clone() returns Self, not Self<'b> when impl'd on some Self<'a>). Reborrowing does not create either an independent or an identical value, since the reborrowed reference has (must have) a strictly shorter lifetime than the original, so:

  • their validity is inter-dependent, because you can't access the reborrow if the original is invalidated, and you can't access the original while the reborrow is active;
  • and their type is not identical, either (the reborrow is a supertype of the original).

I agree that &mut T doesn't implement Clone and Copy, but the way I interpret it is that it's because Copy has stronger requirements than just being able to be copied -- it must be able to be copied and never affect the borrow checker rules. If &mut T was Copy, the borrow checker would have no way to check generic code.

The whole point of subtyping though is that two different types can be in an "is a" relationship. Self<'a> is a Self<'b>.

A violation of this would mean that the lifetimes are not contained within each other, so one type wouldn't be a subtype of the other.

And this one is what the borrow checker verifies.

Now: If you still claim that reborrowing is a coercion, I don't see how to square my last example with the rules about coercion sites. Are those rules incomplete?

Hmm apparently they are incomplete:

    let s: String = "hello".into();
    let s_ref: &String = &s;
    let mut a: &str = s_ref;
    a = s_ref; // Why does this work??

Reborrowing was put forward as an important coercion/autoborrow in 2013 (but not officially documented now).

I don't get what you are trying to say here or why would this assertion of mine be wrong. Clearly the reborrow's lifetime is a subset of the original, because it can't be used for extending the scope of its referent (as no reference can). This failing to compile demonstrates that the reborrow is still a borrow of the original, and doesn't allow mutating their common referent, even though the original borrow is not itself used.

I wasn't saying that your assertion was wrong, just that it didn't invalidate my interpretation.

I believe it is incomplete as far as reborrows go (but not necessarily other coercions?). It's probably somewhat fractal like many things in Rust.

Reborrows happen on assignment as per the nll rfc if nothing else.

(Not in a position to experiment much or provide better citations unfortunately.)

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.