Since not that long ago rust-analyzer has added implicit calls to their hints, i have noticed something puzzles me a bit. When i write something like let foo: &str = &format!("bar") i think i understand what's going on - a reference to String is a &str, makes sense.
At the point where i am i also understand that in reality there's some dark dereference magic happens under the hood and in reality it's more like let foo: &str = &*format!("bar"), where String is getting dereferenced into str, of which i then take reference.
What puzzles me, however, is that the hint that rust-analyzer shows is... let foo: &str = &**&format!("bar"). What... is even going on here? What the heck is &**&?
Trying to go step by step and looking at the type hinds shows: let foo: String = format!("bar") let foo: &String = &format!("bar") let foo: String = *&format!("bar") with a "cannot move out of a shared reference" error. What? Why? let foo: str = **&format!("bar") also with a much more understandable error of str having an unknown size so i can't just plop into into a stack stored variable. But also thinking about it, how does it know to dereference it like this here? let foo: &str = &**&format!("bar") and then we arrive to this.
BUT, as soon as i thought i reached the bottom, i thought "hang on, of i get str from **&String, and *& is a moot operation that just takes a reference of value and then gets the value back by dereferencing said value... shouldn't the *String also give me the str, and, by extension, shouldn't &*String also result in &str? let foo: &str = &*format!("bar") and it does! In fact, let foo: &str = &*format!("bar") and let foo: &str = &**&format!("bar") seem to all have an identical result, except second one being more verbose. And both result in a suggestion to simplify it to let foo: &str = &format!("bar"). With one minor difference - when i write let foo: &str = &*format!("bar") - i do not get an implicit hint telling me that in reality it's a &**&, not just &* like it does with just &.
So... what exactly is going on here? Why two different explicit ways of writing the same reference cast? What's the deal with *& error and why is it a part of that conversion chain? Why isn't the hint about what compiler really is doing the &* since it seem to result in the same outcome as &**& and doesn't produce a hint telling me that in reality there's also this confusing *& happening before the &*? I'm completely and utterly lost.
The first thing you need to understand is places. (If you've heard of “lvalues” from other languages, it's the same idea.) A place is some piece of memory designated by some Rust expression. The thing on the left side of = is a place expression, so the most common situation where you are doing things with places is assignments:
let mut x = 1;
x = 2; // x is a place
let mut x = 1;
let xref = &mut x;
*xref = 2; // *xref is a place
*"bar" is a place of type str. You can't store it in a variable because it doesn't have a fixed size, but you can still take a reference to it — putting & or &mut before any place gives you a reference to it.
The reason you're getting weird-seeming results with * is mostly that some places are unsized and so &*foo is valid in some situations where *foo isn't.
let foo: String = *&format!("bar") with a "cannot move out of a shared reference" error. What? Why?
Because using * on a place in a regular value context, like the right side of =, means to move or copy out of the place. String isn't Copy so this isn't a copy, and you can't move out of an &, so this is an error.
In general, *&foo is the same as fooif it succeeds. But there are reasons it can fail.
You are likely confusing Deref coercions with regular referencing/dereferencing.
In the absence of Deref coercions, & and * are inverses of each other: *&value designates the value itself, and &*reference is the same as reference. If Deref coercions come into play, then *reference might actually mean *reference.deref() (and so &*reference might mean reference.deref()) sometimes. It's a "sometimes" because explicit type annotations (among other things) can influence whether the compiler inserts a Deref coercion.
As you observed, it's just a completely normal chain of reference and de-reference operations.
You are not allowed to move out of a reference, because then the reference would point to invalid memory, which isn't allowed by the language.
I don't think anything like that is happening. Rust-analyzer is not the compiler, and its analyses are markedly not identical to those of the compiler. It's likely being overzealous in this case. In a simple deref coercion, the conversion &String -> &str happens automatically, which involves no dereferencing of a "real" reference, it's desugared to something like Deref::deref(&the_string) or the_string.deref().
They might in fact be using the compiler. I'm not too familiar with the exact internals and implementation details of rust-analyzer, but it is my impression that type mismatches, incorrect suggestions, and a whole range of other typing-related bugs are significantly more frequent than what I would expect from software that is not re-implementing type checking.
It might "only" be an issue with presentation and/or parsing the compiler's output, however I regularly encounter questions on URLO whereby people who use rust-analyzer are confused, because it suggests/shows complete bollocks, whereas suggestions and errors coming from a command line invocation of cargo check (on identical code) are at least technically correct (even if not always perfectly helpful).
The fact that I have never seen &**& suggested by the compiler before corroborates my claims.