Sling Academy
Home/Rust/Strings in Rust: Owned String vs Borrowed &str

Strings in Rust: Owned String vs Borrowed &str

Last updated: January 03, 2025

When working with strings in Rust, it is crucial to understand the difference between the two primary string types: String and &str. Both types are used to represent string data but differ in terms of ownership, mutability, and memory allocation.

Owned String: String

The String type represents an owned, growable string. It is allocated on the heap, allowing the string to be modified in size, and thus, makes it ideal when you need a string whose size might change or when you need ownership over the string data.

Here's how you can create a String in Rust:

fn main() {
    let mut s = String::from("Hello");
    s.push_str(", World!");
    println!("{}", s);  // Output: Hello, World!
}

As you can see, the String::from function can convert a &str into a String. Moreover, because String is mutable (assuming you've declared it as mut), we were able to append additional text using push_str.

Borrowed String Slice: &str

The &str type, often referred to as a string slice, is a borrowed reference to a string. It can refer to a part of a string or a full string, but its size and contents cannot be modified. String slices are generally used for read-only access to string data, and since they're not responsible for managing the string data, they're more lightweight in terms of memory allocation.

Here's an example of using &str:

fn main() {
    let phrase = "Hello, Rust!";
    let hello: &str = &phrase[..5];  // Borrowing a string slice
    println!("{}", hello);  // Output: Hello
}

In this code snippet, phrase is a &str that borrows directly from the static string literal, and hello is a slice of that string. The slice is limited to the first five characters.

Conversion Between String and &str

Often, you’ll find yourself needing to convert between String and &str types. Rust provides several methods to facilitate this conversion smoothly.

To convert from String to &str, you can use the as_str method or simply borrow the reference:

fn main() {
    let s = String::from("Hello, Rust!");
    let s_slice: &str = &s;  // Borrowing to convert
    println!("{}", s_slice);
}

To convert from &str to String, use to_string or the String::from function:

fn main() {
    let slice: &str = "Hello, Rust!";
    let s: String = slice.to_string();  // Owned conversion
    println!("{}", s);
}

Performance Considerations

When designing Rust applications, consider your choice between String and &str for balanced performance and memory usage. Since String allocations happen on the heap, they come with allocation and deallocation overhead. Spin up String objects when you need mutability or ownership.

On the other hand, string slices are efficient for referencing existing string data without copying, utilizing minimal memory resources. Therefore, prefer &str for function parameters when mutation is unnecessary.

Conclusion

Understanding the differences between String and &str enables you as a Rust developer to manage memory effectively and adhere to Rust's ownership principles. By leveraging these types wisely, you can craft powerful applications with rich text manipulation features while ensuring maximum runtime efficiency.

Next Article: Cow (Copy-On-Write) for Optimized String Handling in Rust

Previous Article: Slices vs Iterators in Rust: Borrowed vs Owned Traversal

Series: Ownership in Rust

Rust

You May Also Like

  • E0557 in Rust: Feature Has Been Removed or Is Unavailable in the Stable Channel
  • Network Protocol Handling Concurrency in Rust with async/await
  • Using the anyhow and thiserror Crates for Better Rust Error Tests
  • Rust - Investigating partial moves when pattern matching on vector or HashMap elements
  • Rust - Handling nested or hierarchical HashMaps for complex data relationships
  • Rust - Combining multiple HashMaps by merging keys and values
  • Composing Functionality in Rust Through Multiple Trait Bounds
  • E0437 in Rust: Unexpected `#` in macro invocation or attribute
  • Integrating I/O and Networking in Rust’s Async Concurrency
  • E0178 in Rust: Conflicting implementations of the same trait for a type
  • Utilizing a Reactor Pattern in Rust for Event-Driven Architectures
  • Parallelizing CPU-Intensive Work with Rust’s rayon Crate
  • Managing WebSocket Connections in Rust for Real-Time Apps
  • Downloading Files in Rust via HTTP for CLI Tools
  • Mocking Network Calls in Rust Tests with the surf or reqwest Crates
  • Rust - Designing advanced concurrency abstractions using generic channels or locks
  • Managing code expansion in debug builds with heavy usage of generics in Rust
  • Implementing parse-from-string logic for generic numeric types in Rust
  • Rust.- Refining trait bounds at implementation time for more specialized behavior