What are the differences between Rust's `String` and `str`?

ghz 21days ago ⋅ 22 views

In Rust, String and str are both used to represent text, but they have some important differences in terms of ownership, mutability, and storage. Here's a breakdown of the key differences:

1. Type and Ownership:

  • String:
    • String is an owned and heap-allocated string type. This means it owns the data it contains, and the data is stored on the heap. It is mutable, so you can modify the contents of the string.
    • String is a growable string type. You can append to it, change its content, and perform various operations that modify it.
  • str:
    • str is a slice type, meaning it is a reference to some UTF-8 encoded string data. It does not own the data; it is merely a view or slice into a string that already exists somewhere in memory (typically either on the stack or in a String object).
    • str is immutable. You cannot modify the data of a str directly.
    • In Rust, str is almost always used as &str, which is a reference to a string slice.

2. Mutability:

  • String:
    • A String is mutable, meaning you can change its content, append to it, and modify it.
    • Example:
      let mut my_string = String::from("Hello");
      my_string.push_str(", world!");
      
  • str:
    • A str is immutable by definition. You cannot change the contents of a str slice.
    • Example:
      let s: &str = "Hello";
      // s.push_str(", world!"); // This would not compile, because `s` is immutable.
      

3. Storage:

  • String:
    • String is heap-allocated. It dynamically grows and shrinks as needed when you add or remove characters. It stores the UTF-8 encoded string data on the heap.
  • str:
    • str is typically stored inline (on the stack) or as part of some other data structure (such as a String). A &str is just a reference, not an owned value.
    • The data behind a str slice can be on the stack or on the heap, depending on where it comes from.

4. Usage:

  • String:
    • Used when you need ownership of the string data, and you want to modify or mutate it.
    • Typically, String is used when you need to create and manipulate strings dynamically, especially when you're unsure of the string's size or content at compile time.
    • Example:
      let mut s = String::from("hello");
      s.push_str(" world");
      
  • str:
    • Used when you want to borrow a string (without taking ownership) or when you have a reference to a part of a string.
    • str is often used in function signatures where you want to take a borrowed string, rather than owning the string.
    • Example:
      fn print_str(s: &str) {
          println!("{}", s);
      }
      
      let s = String::from("hello");
      print_str(&s);  // Pass a reference to `String` as `&str`
      

5. Conversion:

  • You can easily convert between String and &str:
    • From String to &str: You can borrow a String as an immutable &str using the as_str() method or simply by using a reference (&).
      let s = String::from("Hello");
      let s_ref: &str = &s; // Borrowing `String` as `&str`
      
    • From &str to String: You can convert a &str to a String by calling the to_string() method or using String::from().
      let s: &str = "Hello";
      let s_string = s.to_string(); // Or String::from(s)
      

6. Memory and Performance:

  • String:
    • Since String is heap-allocated, creating a String is generally more expensive in terms of performance and memory usage. However, String is flexible and suitable for dynamic operations like resizing, concatenation, etc.
  • str:
    • A &str is a lightweight, non-owning reference to string data, which is very efficient when you're working with data that doesn’t need to be modified or owned. You don’t pay the cost of heap allocation, so it is faster for cases where no modification is needed.

Summary Table:

FeatureStringstr (or &str)
TypeOwned, heap-allocatedImmutable, reference to data
MutabilityMutableImmutable
StorageHeap-allocatedStack or part of another structure
Use caseWhen you need ownership and mutabilityWhen you just need a reference
ConversionCan be converted to &strCan be converted to String

When to Use Each:

  • Use String when you need to own, mutate, or modify a string. It's more flexible but comes with the overhead of heap allocation.
  • Use &str when you just need to reference a string, pass it around, or perform operations that don't require modifying the string itself. It’s more efficient for passing around immutable data.

Both types are essential in Rust's ownership model, and you will often find yourself using both depending on whether you need ownership or just a reference.