Choosing Between str and String in Rust

Choosing Between str and String in Rust

In this guide, we’ll explore the reasons for having two types of strings in Rust &str and String , how they work, how they are stored in memory, and when to use each of them.

Grab your pop corn and let’s ride along!

Why do we have two types of string in Rust?

It’s normal for new Rust developers to ask this question: “Why do we have two types of strings in Rust?” Which is a valid question because most languages have just one type of string. While Rust is strict on rules, the language also tries to strike a balance between flexibility, safety, and performance.

The String type is is owned, growable, and a mutable string type that provides high-level functionality, flexible, but might come with some performance issues and still safe.

While &str is a view into part or all of a string, it’s not owned, it’s borrowed, immutable and provides low-level efficiency and of course, still safe.

So, depending on your use case, you have the leverage to choose the one that works best for you.

How String is stored in the memory

String Metadata (the pointer, the capacity, and length) is stored in the stack with a pointer to the heap where the actual data is stored.

How &str is stored in the memory

The way &str (string slices) are stored in memory depends on whether they are string literals or slices of a String

String Literals:

  • String literals (e.g., let s = "Hello") are stored directly in the read-only data section of the program’s executable.
  • They are hardcoded into the binary and cannot be modified at runtime.
  • The &str for a string literal essentially points to the location of the literal in the program’s read-only memory.

String Slices of a String

  • When you create a string slice from a String (e.g., let slice = &my_string[0..2]), the slice &str is just a reference to the underlying byte array stored on the heap for the String.
  • The slice itself doesn’t own the data; it’s just a view into the String data.
  • The slice stores the pointer to the starting byte of the slice within the String, along with the length of the slice.

When should you use `str` instead of the `String`?

  • Use `&str` when you don’t plan to modify the text. If you’re working with read-only text, using `&str` can be more memory-efficient as the string slice does not involve memory allocation and deallocation
  • When you need a string that only hold references to values already in memory.
  • Use string slice if you don’t need to transfer ownership of a text to a function or variable, it doesn’t own the string, it only holds a reference to a memory address that contains the string.

Here is an example of how you can use a string slice:

fn main() {
   let s = String::from("Hello, world");

   let greeting = "Hello, Rust!"; // string literal, also a string slice but stored in the program executable
   
   let greetings2 = &s[0..]; // string slice, has a reference to the main String in the heap

   println!("{}, {}", greeting, greetings2);
}   

let greeting = "Hello, Rust!";: This line defines a string literal, which is a string slice stored directly in the program’s executable. String literals are immutable and have a fixed size at compile-time

let greetings2 = &s[0..];: This line creates a string slice greetings2, which is a view into the String instance s. The slice starts at index 0 and extends to the end of the string. The & symbol creates a reference to the underlying data in s

When should you use a `String`

The simple and short answer is – to use String when you need to modify, build, or own text data, especially if you need dynamic string manipulation like the example below:

fn create_greeting(name: &str) -> String {
   let mut greeting = String::from("Hello, ");
   greeting.push_str(name);
   greeting
}

fn main() {
   let person = "Alice";
   let personalized_greeting = create_greeting(person);
   println!("{}", personalized_greeting); 
}

Things to note:

String contains heap-allocated bytes. While working with String, you’ll most likely allocate and deallocate memory which might come with a small cost especially if you are working with a large string. When you use the `format!` macro, you perform a memory allocation. Smartstring seems to help you find a way around this:

Smartstring are String type that’s source compatible with `std::string::String`, uses exactly the same amount of space, doesn’t heap allocate for short strings (up to 23 bytes on 64-bit archs) by storing them in the space a `String` would have taken up on the stack, making strings go faster overall.

Both string types are very relevant in their own capacity — there is more to learn about strings in Rust. The Rust Book is a great place to learn more https://doc.rust-lang.org/book/ch08-02-strings.html.

Happy hacking!

Buy Me A Coffee

Published by Eze Sunday Eze

Hi, welcome to my blog. I am Software Engineer and Technical Writer. And in this blog, I focus on sharing my views on the tech and tools I use. If you love my content and wish to stay in the loop, then by all means share this page and bookmark this website.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.