Understanding Strings in Rust: String vs str
Tags: rust, programming, python, learning
Published: 2023-07-10 18:01:05

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](https://doc.rust-lang.org/std/string/struct.String.html)).

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](https://doc.rust-lang.org/std/primitive.str.html)).
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:

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

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`:

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

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:

```rust
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);
}
```
This program will compile and print "hello!".

**Example of using `&mut str`**
```rust
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);
}
```

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`](https://doc.rust-lang.org/std/string/struct.String.html) and [`str`](https://doc.rust-lang.org/std/primitive.str.html).