In short: the * triggers an explicit deref, which can be overloaded via ops::Deref.
More Detail
Look at this code:
let s = "hi".to_string(); // : String
let a = &s;
What’s the type of a? It’s simply &String! This shouldn’t be very surprising, since we take the reference of a String. Ok, but what about this?
let s = "hi".to_string(); // : String
let b = &*s; // equivalent to `&(*s)`
What’s the type of b? It’s &str! Wow, what happened?
Note that *s is executed first. As most operators, the dereference operator * is also overloadable and the usage of the operator can be considered syntax sugar for *std::ops::Deref::deref(&s) (note that we recursively dereferencing here!). String does overload this operator:
impl Deref for String {
type Target = str;
fn deref(&self) -> &str { ... }
}
So, *s is actually *std::ops::Deref::deref(&s), in which the deref() function has the return type &str which is then dereferenced again. Thus, *s has the type str (note the lack of &).
Since str is unsized and not very handy on its own, we’d like to have a reference to it instead, namely &str. We can do this by adding a & in front of the expression! Tada, now we reached the type &str!
&*s is rather the manual and explicit form. Often, the Deref-overload is used via automatic deref coercion. When the target type is fixed, the compiler will deref for you:
fn takes_string_slice(_: &str) {}
let s = "hi".to_string(); // : String
takes_string_slice(&s); // this works!