In Rust, String and str (usually used as &str) are both used to handle UTF-8 encoded strings, but they serve distinct purposes. Here’s a detailed breakdown of their differences, along with examples:
1. Ownership and Mutability
Aspect
String
str (&str)
Ownership
Owned type (heap-allocated).
Borrowed slice (reference to UTF-8 data).
Mutability
Mutable (can grow/shrink).
Immutable (fixed view into data).
Storage
Heap-allocated buffer.
Points to data in heap, stack, or binary.
Example:
// String: Owned, mutable
let mut s = String::from("hello");
s.push_str(" world"); // Modify the String
// &str: Immutable reference
let s_literal: &str = "hello"; // Static string slice (stored in binary)
let slice: &str = &s[0..5]; // Borrowed slice from a String
2. Memory Representation
String:
Contains a pointer to the heap-allocated buffer, a length (current data size), and a capacity (allocated space).
Example: String::from("hello") allocates memory on the heap to store "hello".
str (&str):
A “fat pointer” containing a pointer to the data and its length.
Does not own the data; it’s a view into existing data (e.g., a String or a static string).
Example:
let s = String::from("hello");
let slice: &str = &s; // Points to the String's heap data
let static_slice: &str = "world"; // Points to static memory (binary)
3. Use Cases
Use Case
String
str (&str)
Modify/own data
Use when you need to mutate or own the string.
Use for read-only access or temporary views.
Function parameters
Pass String to transfer ownership.
Pass &str to avoid unnecessary copies.
Storing text
Store dynamic text (e.g., user input).
Reference fixed text (e.g., literals).
Example (Function Parameters):
// Accepts &str for flexibility
fn print_text(text: &str) {
println!("{}", text);
}
let s = String::from("hello");
print_text(&s); // Coerces String to &str
print_text("world"); // Directly pass a literal &str
4. Conversions
&str to String (Clone/Allocate):
let s_slice: &str = "hello";
let s_string: String = s_slice.to_string(); // or String::from(s_slice)
String to &str (Borrow):
let s = String::from("hello");
let s_slice: &str = &s; // Implicit deref coercion (String → &str)
5. Methods and Traits
String:
Methods to modify the string: push_str, pop, clear, etc.
Implements Deref<Target = str>, so all str methods work on String.
str (&str):
Read-only methods: len, contains, split, etc.
Cannot modify the underlying data.
Example:
let mut s = String::from("rust");
s.push_str("acean"); // Modify the String
assert_eq!(s, "rustacean");
let slice = &s[0..4];
assert_eq!(slice, "rust"); // Read-only slice
6. Lifetimes
String:
Owned data: no lifetime annotations needed.
Valid until it’s dropped (goes out of scope).
&str:
Borrowed data: requires explicit lifetime annotations if not static.
Must not outlive the data it references.
Example:
fn get_slice<'a>(s: &'a String) -> &'a str {
&s[0..2] // Slice tied to the String's lifetime
}
let s = String::from("hello");
let slice = get_slice(&s); // Valid as long as `s` exists