String
is indeed heap-allocated so x
is indeed an (owning) pointer, though some people may prefer not to use the term “pointer” (or “smart pointer”) for collection types like String
or Vec<u8>
, and to limit it to APIs that point to a single piece of data, like for instance Box<T>
or Arc<T>
/Rc<T>
.
More precisely, x
consists of 3 parts, the pointer to the heap memory, the length of the allocation, and the length of the currently initialized portion of the string.
Writing &x
creates a pointer-to-a-pointer in a sense, i.e. it’s double-indirection, and its type is &String
. &str
is a different type, but via a so-called “deref coercion”, &String
can (and in your code example is) implicitly coerced into &str
. The type &str
does no longer have double-indirection; so the implicit coercion removes one level of indirection. The str
type identifies the string data (wherever it may be stored) directly, and is a so-called “unsized” type; the pointer to it, the reference &str
, becomes a “fat pointer” consisting of a pointer to the data, and an additional piece of information, in this case the length of the string data being referenced. (Other types involving str
are e.g. &mut str
and Box<str>
, Arc<str>
or Rc<str>
, etc.
Click here for more details on how deref coercion operates and desugars.
The deref coersion that turns a value foo: &String
into &str
would more explicitly be written as the operation &**foo
, which
- dereferences the
&String
into String
, then
- dereferences the
String
into str
, and finally
- creates a
&str
reference
though that view is more of a typesystem-based view involving intermediate results called “places” not “values” - operationally it’s more like doing a single pointer dereference, copying the String
’s pointer field and length field. The *
operator that turns String
into str
is desugared to *Deref::deref(&…)
so the whole &**foo
thing becomes &*Deref::deref(&*foo)
. The remaining *
operations operate on &T
references directly, and are essentially cancelled by the preceding &
operators, so we end up with the operation Deref::deref(foo)
. This makes sense, as the signature for Deref::deref
implemented on String
is fn(&String) -> &str
anyways.
Alternatively, instead of relying on the implicit coercion, you could also write
let y = x.as_str();
using the String::as_str
method, to achieve the same effect.
Funnily enough, if you (click “source” on its documentation to) look at its source code, it’s implemented in terms of the implicit coercion once again, resulting in the amazing source code
impl String {
pub fn as_str(&self) -> &str {
self
}
}