"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 newString
you can clone it either byto_owned()
orto_string()
(they are effectively the same - use whichever makes your code clearer to read and consistent). These will copy the memory and make a newString
.
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).
Top comments (3)
This is so helpful. Thanks for explaining in detail.
I have started learning RUST and I was solving some coding problems. Thanks for writing this. It is really helpful.
Thank you.