fn main()
{
{
let mut _a = 11;
let mut _b = 33;
let mut _c = 55;
let mut mut_ref_a = &mut _a;
let ref_ref = &mut mut_ref_a;
//let x = *ref_ref; // NO
let x: &mut i32 = *ref_ref; // YES
*x = 123;
println!("x: {}", x);
println!("_a = {}", _a);
}
}
Why does the compiler complain if I don't explicitly specify the type of variable x? For completeness: if I don't specify the type, the IDE still indicates the correct type with x: &mut i32 = ...
This is not a problem of type inference, but rather of implicit reborrowing.
When you have a &mut &mut T, you can't just move the internal &mut T out - this will leave the outer reference dangling. So what you can do is reborrow - that is, if you replace the failing line with let x = &mut **ref_ref;, it compiles, since in this case the borrow is not moved, but only temporarily locked.
This reborrowing process can also happen automatically, and that's what you see in your original working case. Although, this implicit reborrowing happens before type inference (logically - I'm not sure how it's done in the compiler, but logically it's this way), that is, to perform a reborrow, the type must be set explicitly, not inferred. Therefore:
when x is explicitly typed, the reborrow happens implicitly, all's good;
when reborrow is inserted explicitly, all's good;
when x is not explicitly typed and there's no explicit reborrow, compiler can't insert it implicitly, so it have to make a move - and this move is illegal.
@Cerber-Ursi described the problem well. I would like to add that this is a rather artificial problem. It doesn’t have to be this way, and there isn’t really anything to learn from this[1] except “Rust’s compiler implementation currently requires somewhat explicit/known types to make implicit re-borrows work”. I personally wish (and I wouldn't know why anyone should be opposed to) this code of yours to become accepted by the compiler without the type annotation, too, at some point in the future.
No, only shared immutable references (&T) are Copy types themselves. Exclusive mutable references (&mut T) aren't, because they can never “alias” i.e. active “mutable references” in Rust may never coexist with other active references (of any kind) to the same target, at the same time. Since Copying a value duplicates it, copying a references creates two references to the same target, which cannot work soundly for &mut T references, only for &T references.
I'm still a little confused by why the implicit move out of *ref_ref can't work; in general a move will invalidate the target, so is it the extra indirection here, or that Rust doesn't know how to invalidate both ref_refand mut_ref_a? I guess it boils down to if *&mut mut_ref_a is legal, but I'm on a phone right now...
Rust never lets you move out of a mutable reference (here ref_ref) regardless of what the target type is — there is no transitive invalidation as you propose in this sentence. It is always true that once a mutable borrow ends, the referent is valid and available for use (or was also invalidated by something else at the same time).
So, every *some_mut_ref which successfully compiles must end up being a copy or a reborrow of the referent (or do nothing, e.g. let _ = *some_mut_ref;).
Implicit reborrows happen so you don't have to write things like this:
fn example(v: &mut Vec<String>) {
// Without a reborrow, `v` would move when calling `v.push`...
(&mut *v).push(String::new());
// ...and this would be an error
println!("{v:?}");
}
I.e. they make &mut _ act closer to as if it were Copy, so you don't have to think about it as often.
They happen when bindings are annotated (as that's a coercion site) and upon overwriting assignments (as part of NLL) and probably a number of other places. The presence of an explicit deref isn't required and I don't think it ever makes a difference. E.g. there is no explicit deref in this version.
I expect implicit derefs for method targets, though annotations being a coercion site helps to understand why they make it work here, too.
I was surprised that Rust would try to reborrow for let foo = *bar; rather than trying to move out of the target of bar and invalidating bar if that would invalidate it's target (eg it's not Copy). Reborrowing if the target is also a ref does make a common use easier, though.
It's function arguments generally, not just method receivers. At least in my mental model, this all fits because arguments are annotated bindings. (But knowing Rust, it's probably not actually that simple, even if it works out the same in this case. )
Oh I meant implicit derefs in general are expected for targets, not just reborrows. I think method/field targets are the only place Rust does arbitrary implicit derefs like that?
Note how it's not the same as what happens during method resolution since it doesn't go all the way down to the owned value. I think[1] it's something like
&[mut] &[mut] T to &[mut] T is fine
&[mut] T to &[mut] <T as Deref>::Target is fine
&[mut] DoesNotImplDeref (or NotARef) is the end of the line
(Note how NotARef would have to autoref to call Deref::deref or DerefMut::deref_mut when NotARef: Deref.)
If you're familiar with the reference, there's a potential point of confusion here (in the past it confused me). It implies that fully-qualified syntax doesn't have autoderef, but that's not true; instead it just acts like a non-method function call. So the receiver type is unambiguous, won't autoref, and won't autoderef all the way down to an owned value, but it still autoderefs.
After spending awhile trying to suss out something simpler, I guess I would take back my "all arguments do autoderef to a point" comment and instead draw this distinction:
auto-deref is going from T to *T
deref coercion is going from &mut T to &mut *T or &mut T to &*T or &T to &*T
note how we have to start with a &[mut] and also end up with a &[mut]
Then I could reply, "the difference is that field access and method receivers use auto-deref, whereas normal arguments use deref coercion".
Or more cases with more words:
Field access (foo.field): has (transitive) auto-deref
lhs, *lhs, **lhs, until it bottoms out
No auto-ref (references don't have fields anyway)
Method resolution (foo.method()): has auto-deref and auto-ref
Same as field access for "initial candidates": lhs, *lhs, **lhs...