DEV Community

Abhishek Mishra
Abhishek Mishra

Posted on • Edited on

Understanding Strings in Rust: String vs str

If you're a Python developer exploring the Rust language, you'll find that working with strings in Rust is quite different from Python. Rust has two main types to represent strings: String and str.

Let's dive into what they are and how they differ.

What are String and str?

String is a growable, heap-allocated data structure that allows you to store a sequence of UTF-8 characters. This is similar to Python's str type, but with one key difference - String in Rust is mutable and can be modified (Rust Doc: String).

On the other hand, str (pronounced 'string slice') is an immutable reference or 'view' into a string. It can point to the entire string or a subsection of it (Rust Doc: str).
You will most commonly encounter str in its borrowed form, represented as &str.
Also, &mut str is a mutable reference to a str. You can use it to perform in-place modifications to the string, as long as those modifications maintain valid UTF-8.

In Rust, str itself is a type that represents a sequence of valid UTF-8 characters, while &str and &mut str are references to such a sequence. The difference is that &str is an immutable reference (meaning you can't use it to change the underlying string), while &mut str is a mutable reference (so you can use it to change the underlying string, as long as you maintain valid UTF-8).

Why does Rust have two string types?

The two string types in Rust cater to different use cases.

String is used when you need to own a string and change its contents, like appending characters or changing a character at a certain position.

str is used for more 'read-only' operations. Since it's an immutable reference, it does not own the underlying memory and cannot alter it.

&mut str allows you to perform some in-place modifications to a string, as long as you maintain valid UTF-8. However, its use is less common and usually requires more care.

However, the mutable operations that you can do on &mut str are limited. For example, as the user mentioned, you can use the make_ascii_lowercase method to change all ASCII alphabetic characters to lowercase in place, but you can't use it to append to the string or change its length, because that could invalidate the UTF-8.

This is a different from Python, where you're used to working with just one main string type (str), which is always immutable.

Example

Let's look at an example:

fn main() {
    let mut s = String::from("hello");
    s.push_str(", world");  // This works fine
    println!("{}", s);
}
Enter fullscreen mode Exit fullscreen mode

In this Rust program, we create a String and then append to it using the push_str method, something Python developers might be surprised to see, given that strings in Python are immutable.

Now, let's see what happens when we try to modify (change a specific character) a str:

fn main() {
    let mut s = "hello";
    s[0] = 'H';  // This will cause an error
    println!("{}", s);
}
Enter fullscreen mode Exit fullscreen mode

In this Rust program, we're trying to replace the first character of the str s with 'H'. If s were a mutable String, we could do this with the replace_range method. But because s is a str, we cannot change its contents, and this program will not compile because str is immutable.

If we want to append a character to the string, we first have to convert it to a String with s.to_string(). Then, we can use the push method to append a character to the String.

For example:

fn main() {
    let s = "hello";
    let mut t = s.to_string();  // Convert to String to allow changes
    t.push('!');  // Append a character to the string
    println!("{}", t);
}
Enter fullscreen mode Exit fullscreen mode

This program will compile and print "hello!".

Example of using &mut str

fn main() {
    let mut s = String::from("HELLO");
    let s_slice = &mut s[..];  // Obtain a &mut str from the String
    s_slice.make_ascii_lowercase();  // This works fine
    println!("{}", s);
}
Enter fullscreen mode Exit fullscreen mode

In this program, we create a String and then obtain a &mut str slice from it using &mut s[..]. We then call the make_ascii_lowercase method on the &mut str to change all ASCII alphabetic characters to lowercase. The program will print "hello".

Conclusion

String and str serve different purposes. Use String when you need to own and modify string data, and use str when you need a temporary, read-only view into a string.

Rust string types give you more control over memory allocation and can help prevent bugs related to memory access. This design choice aligns with Rust's core goals of performance and safety. It's one of the ways Rust differs significantly from Python and many other languages, and it's something you'll get used to as you write more Rust code.

For more detailed information, refer to the official Rust documentation for String and str.

Top comments (0)