Undestanding str and ownership

I'm learning Rust, I have quite understood ownership and all the stuff correlated, but trying to solve this exercise I found something that create me some confusion.
Hopefully you can help me understanding this topic better :slight_smile:

The exercise is this the first one of the Rust by Practice Ownership chapter.

fn main() {
    // Use as many approaches as you can to make it work
    let x = String::from("hello, world");
    let y = x;
    println!("{},{}",x,y);
}

The easiest way I thought is to clone x using x.clone(), which works.
But when I tried other ways to solve I've managed to make it work with this syntax:

fn main() {
    // Use as many approaches as you can to make it work
    let x = String::from("hello, world");
    let y : &str = &x;
    println!("{},{}",x,y);
}

The confusion I have is about the syntax I have to use to make this compile.
x is a String, so x is actually a pointer to x.
y is a pointer to str. So why I need to write:

&str = &x;

and not:

&str = x;

Since x is a pointer &x wouldn't be a pointer of a pointer?

This isn't really correct. The type of x is String - it's not a pointer[1].

So it makes sense that you need to have a & to assign to y, as otherwise you'd be assigning a reference to a non-reference typed variable.

One other thing that may not be clear - why can you assign a &String to a variable that's &str? This is because String 'dereferences' to str, which (among other things) means that the compiler can automatically convert references to the former into references to the latter.

Later chapters of the book will cover this in more detail!


  1. If you want to get more technical, a String contains a pointer to some string data that lives on the heap, but that's a low level detail you don't really need to care about. Also, as an aside, the term for & types is a reference, not a pointer - it's worth distinguishing between the two, as C-style pointers do exist in Rust as well (albeit only in unsafe code). ↩︎

7 Likes

No. x is only ever x. It's never a pointer to x. A pointer (reference) to x is spelled &x. There is no magic – everything is what it is.

Why do you think that x is actually "a pointer to x" (itself)?

2 Likes

Ok thank you. I thought that since String type is allocated into the heap, what we get is not the actual data but the pointer to the data. So I was thinking that x is actually a pointer and not the data itself.

To better undestand...

Why if I try to print a String s and the its reference &s I get the same result?

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
    }
}
6 Likes

x is a String. It has type String, which is a struct defined in the standard library. It is not a pointer.

The string type contains a Vec<u8> which in turn contains a pointer to a heap-allocated buffer. This still doesn't mean that it itself is a pointer. It's a struct, containing a pointer, among others (a length and a capacity as well). Containing a pointer and being a pointer are not the same thing.

The only thing that's heap-allocated in a String is the buffer of the bytes it owns. When you declare a String, you can still put the String instance itself (not the byte buffer!) on the stack – this is in fact exactly what happens if you simply declare a local variable of type String.

Because Display is blanket-implemented on references in a way that they always forward to the referenced value. This has nothing to do with String; you can try and experience the exact same phenomenon if you try to print a &u32 or a &io::Error or any other reference of which the referent type is Display.

5 Likes

Incidentally, the heap vs. the stack (vs. any other memory segment) has nothing to do with whether you get pointers. A pointer can point to the stack, or to the heap, or into static memory, or wherever it pleases really. The fact that you have a pointer of some sort emphatically does not mean that you are dealing with heap-allocated memory.

3 Likes