String Literals vs. String Objects in Rust

There are two types of strings in Rust: string literals and string objects. String literals are primitive types, while string objects are non-primitive.

In order to understand these two types of strings, it is helpful to have an understanding of the stack and the heap, which are covered later in this course but are not difficult to comprehend.

In this tutorial, we will cover string literals and string objects. We will see how and why they are different – both in terms of how they can be used as well as how they operate under the hood. In order to understand string literals and string objects, we first need a basic overview of the stack and the heap.

Strings and The Stack and The Heap

The stack and the heap are the two primary data structures used by the Rust compiler. When it comes to strings, string literals are stored on the stack while string objects are stored on the heap.

Note that the following is just an overview; for more details on the workings of the stack and heap, see the dedicated tutorial.

Main Article: The Stack and The Heap

The Stack

The stack is a data structure that allows for very fast storage and retrieval but is limited in terms of how data is stored and retrieved.

The stack is like a stack of books.

We use the analogy of a stack of books arranged vertically; books can be ‘pushed’ onto the top of the stack or ‘popped’ off the top of the stack. But we can’t access the books in the middle of the stack.

String literals can be pushed onto or popped off the stack.

In Rust, primitive types like string literals are stored on the stack, where they can be stored and retrieved very quickly.

The Heap

The heap is a data structure that allows large and unknown amounts of information to be stored, but is slower than the stack.

The heap is like a bookshelf.

You can think of the heap as a bookshelf with a large amount of books arranged horizontally. In this analogy, books in the middle of the shelf can be accessed, but we need to know where they are. If we want to retrieve a collection of books, we also need to know what books they are, how many to retrieve, etc. When finding a book in a library, you wouldn’t want to start from the first book and iterate through every single book; it’s much faster to use an index combined with an organizational system so that we can quickly identify books.

String objects stored on heap

In Rust, non-primitive types like string objects are stored on the heap but a pointer to their location, as well as the size of the memory allocation, are stored on the stack.

Pointer to string on heap

Retrieving an object from the heap always slower than the stack because the compiler has to follow the pointer from the stack to the location in the heap, and then retrieve the data.

String Literals

String literals are primitive data types. They are immutable and have a fixed length that is known at compile time. This allows them to be stored on the stack for fast retrieval.

A string literal is created anytime we use double quotes to encapsulate a sequence of Unicode characters:

let s = "Hello, world!";

However, what if we need a more complex string type that can be mutated (changed) as the code runs? In this case, the size of the string will not be known at compile time and thus it can’t be stored on the stack (see below for more information on this).

This is where string objects come in handy.

String Objects

String objects are non-primitive data types. Their size is not known at compile time, which means that they can’t be stored on the stack. However, they are mutable.

When a string object is created, the string itself is stored on the heap. After the string gets stored on the heap, the memory location at which it is stored (i.e. the location on the heap) is returned and stored on the stack. This is because the compiler needs to know where the string is stored.

So while we often say that that ‘string objects are stored on the heap’, what we mean is that it is partially stored on the stack, and partially stored on the heap.

The information on the heap includes:

  • The actual string itself.

The information on the stack includes:

  • A pointer to the location of the string on the heap.
  • The actual length of the string.
  • The capacity of the string object – string objects are mutable, so the capacity must always be larger than the length. If a string is made longer than the length, then more space on the heap will need to be allocated to allow for this.

There are a few different ways to create a string object. First, we can pass apply the .to_string() method to a string literal:

let s = "Hello, world!".to_string();

Despite being a string object, this string is still not mutable; we can make it mutable using the keyword mut in the variable declaration:

let mut s = "Hello, world!".to_string();

Alternatively, we can use the String::new() method to create an empty string object. Then we can use the push_str() method to populate the string:

fn main() {
    let mut s = String::new();
    s.push_str("Hello, world!");
    println!("{}",s);
}

Standard Output:

Hello, world!

String Literals vs. String Objects in Rust

The following table summarizes the properties of string literals vs. string objects.

String LiteralString Object
Data TypePrimitiveNon-Primitive
Data AllocationStackHeap
MutabilityImmutableMutable
Size at compile timeKnownUnknown
Copy or moveCopy typeMove type

String Copy and Move Types

Rust has strict rules regarding when a value is copied versus moved. Primitive types get copied, while non-primitives get moved.

This means that string literals are considered a copy-type, while string objects are considered a move-type.

See the main article on copy types vs, move types for more information about this:

Main article: Copy vs. Move Types