DEV Community

loading...
Cover image for Rust String vs &str

Rust String vs &str

stevepryde profile image Steve Pryde ・5 min read

"Why does Rust have different types of Strings? Why not just one String type?"

I have seen these questions posed in different ways online and it is true that Rust's String and &str types can be confusing for people new to Rust, especially people who are familiar with dynamic programming languages. So let's talk about the two string types and see if we can clear up some of the confusion.

It will make sense eventually and Rust really does need both types. They are quite different and have different use cases.

TL;DR

It's all about "who owns the memory".

String

  • String contains a string in memory and owns the memory for it.

  • Use String for returning strings created within a function or (usually) when storing strings in a struct or enum.

  • If you have a String you can pass a reference to it to convert it to &str.

&str

  • &str is just a reference to another string (slice) but does not own the memory for it.

  • Prefer &str in function arguments to accept string slices and make it clear the function will not mutate the string.

  • If you have a &str and want a new String you can clone it either by to_owned() or to_string() (they are effectively the same - use whichever makes your code clearer to read and consistent). These will copy the memory and make a new String.

It's about memory and ownership

Firstly, we need to talk a little about how Rust manages memory. I'll try to keep this brief but it will be very important later when we discuss the difference between String and &str.

As you may be aware, Rust does not have a garbage collector. That means, the compiler needs to explicitly allocate and deallocate memory at specific points in the code, and it needs a mechanism for determining when something is no longer in use so that its memory can be deallocated safely.

This mechanism in Rust is called "ownership" (and "borrowing"). Consider the following code:

fn main() {
    let owned_string = get_string();
    print_string(&owned_string);
}

fn get_string() -> String {
    let s = String::from("Hello, World!");
    // In Rust we return s by omitting the semicolon
    s
}

fn print_string(my_string: &str) {
    println!("{}", my_string);
}

Here in get_string() we allocate memory for a String on the heap (to learn more about what the heap is, see here) and assign it to the variable s. At this point we can say that s "owns" that memory. The Rust compiler will now track the scope of s in order to know when to deallocate it. This would occur when s goes out of scope, unless s is instead "moved" (which you can think of like a transfer of ownership). In the above case, s is indeed moved, into the variable owned_string at the top.

When get_string() returns, the memory isn't copied and it doesn't go anywhere. Rather, the ownership of that memory is now transferred to the caller (in this case the variable owned_string).

The Rust compiler would now track this new owner owned_string in order to know when to deallocate. In this case it would be deallocated at the end of the main() function.

A String will only ever have 1 owner. That owner can allow others to "borrow" it temporarily (see the line print_string(&owned_string);) , but the memory will only be deallocated when the owner goes out of scope (unless it is moved, in which case it will be deallocated when the new owner goes out of scope, and so on).

Borrowing and references

So now let's look at the other function:

fn print_string(my_string: &str) {
    println!("{}", my_string);
}

The & symbol means this is a reference to a str (a string slice). You will almost never use str without & so it's easier to just think of them as &str, or "a reference to a string slice".

Consider the String we had earlier with its owned, allocated memory. What if we wanted to pass a reference to that string (or part of it), but without allowing the recipient to modify it. In Rust we call that an "immutable borrow", and that is what the & means. It is like a "reference" in other languages, but Rust goes a step further and lets you declare whether or not its contents can be mutated (changed).

In the above example we could have used &String in the function signature instead of &str and that would have worked ok because we passed in the entire string anyway. However, that wouldn't allow other callers to pass in a substring (at least without copying its memory into another String). This is where a string slice &str is much more useful.

&str is effectively a pointer directly to the string's memory, with a fixed length. You will notice in the original code the line print_string(&owned_string);. We passed a reference to a String into the function, but the function wanted &str. This works because the String type can automatically convert into &str - this is just returning a pointer to the very same memory, with a known length. &str is also an immutable reference so you cannot modify the memory it points to.

In fact, Rust will ensure that the &str is safe to use (even across threads!) by guaranteeing that nothing can modify the memory it points to (even the owner), for as long as the & reference variable is in scope. Rust will also guarantee that the owned String (that the &str points to) cannot go out of scope while the reference remains in scope (otherwise the reference would be a dangling pointer). These are very powerful features of Rust's borrow checker that prevent all kinds of nasty bugs you might encounter with other languages where you create a string over here then pass a reference to it over there but it accidentally modifies the original.

The difference between String and &str is...

String holds a string in memory and owns the memory for it.

&str is just a reference to another string but it doesn't own the memory for it.

When to use either one

In general, use String in structs and enums where you want the struct or enum to own the contents. Also use String when returning a string from a function where that string was allocated within the function (Rust will not let you return a reference in that case because the memory would be deallocated at the end of the function and the reference returned would be a dangling pointer)

Prefer &str for function arguments unless you explicitly want to move a String into the function and give up ownership of it (this tends to be rare).

One last time...

Use String when you need to own the memory, for example you created a string in a function and need to return it.

Use &str when you want an immutable reference to memory that is owned by another String variable (or a string literal in your code).

Discussion (1)

pic
Editor guide
Collapse
email2vimalraj profile image
Vimalraj Selvam

This is so helpful. Thanks for explaining in detail.