The string literal, "Hello, World!" , is stored somewhere in read-only memory, (neither in the stack nor heap), and a pointer to that string is stored on the stack. Because it’s a string literal, it usually shows up as a reference, meaning that we use a pointer to a string stored in permanent memory, (see Ownership in Rust, Part 2 for more on references), and it’s guaranteed to be valid for the duration of the entire program, (it has a static lifetime).

Here, the pointers stored in hello and hello1 are using the stack. When we use the = operator, Rust pushes a new copy of the pointer stored in hello onto the stack, and binds it to hello1 . At the end of the scope, Rust adds a call to drop which pops the values from the stack in order to free up memory. These pointers can be stored and easily copied to the stack because their size is known at compile-time.

Move trait when using the heap

Over on the heap, the String type with value "Hello, World!" is bound to the variable hello , using the String::from method. However, unlike the string literal, there’s more data bound to hello than just a pointer, and the size of this data can change during runtime. Here, the = operator binds the data from hello to a new variable hello1 , effectively moving the data from one variable to another. Poor hello is now invalid, as per ownership rule #2: “There can only be one owner at a time.”

But why do this? Why doesn’t Rust always just make a copy of the data and bind it to the new variable?

If we think back to the differences between the stack and heap, we remember that the size of data stored on the heap is not known at compile time, which means we need to run through some memory allocation steps during runtime. This can be expensive. Depending on how much data we’re storing, we could quickly run out of memory if we sit around making copies of data all day.

Besides that, the default behavior of Rust helps protect us from memory issues that we might run into in other languages.