5/5
## Mastering Strings in Rust: `String` vs. `&str` Welcome to this comprehensive guide on handling text in Rust. Understanding how Rust manages strings is fundamental to writing efficient and safe code. Rust offers two primary string types: `String`, an owned, heap-allocated string, and `&str` (pronounced "string slice"), which is a reference to string data. This lesson will delve into their characteristics, use cases, creation, manipulation, and how they interact. ## Understanding Rust's Primary String Types: `String` and `&str` At the heart of Rust's string handling are `String` and `&str`. Let's explore each one. 1. **`String` (with a capital 'S')** * **Nature:** `String` is an owned data type. This means when you have a `String`, your variable directly owns the string data, which is stored on the heap. This allows the string to be growable and modifiable. * **When to use `String`:** * **Ownership is required:** When the string data needs to persist longer than the current function call or scope, or if it needs to be returned from a function, `String` is the appropriate choice. * **Mutability is needed:** If you intend to modify the string (e.g., append characters, clear its contents, insert substrings), you need a `String`. 2. **`&str` (String Slice)** * **Nature:** `&str` is a borrowed type, specifically a "slice." It's an immutable reference to a sequence of UTF-8 encoded bytes. Think of it as a "view" into string data that could be owned by a `String`, or it could be a string literal embedded directly in your program's binary. * **String Literals:** When you write `let message = "hello";`, the type of `message` is `&'static str`. The `&` indicates it's a reference, `str` indicates it's a string slice, and `'static` is a lifetime annotation signifying that this string data is valid for the entire duration of the program. * **When to use `&str`:** * **Read-only access:** When you only need to read or inspect string data without modifying it. * **Working with string literals:** String literals are inherently `&str`. * **Function parameters (flexibility):** It's often preferred for function parameters when the function only needs to read the string. This is because `&str` can accept both string literals and references to `String`s due to a feature called deref coercion, making your functions more versatile. ## Working with `String`: Creation and Basic Operations Let's look at how to create and perform basic operations on `String` types. **Creating a `String`** There are several idiomatic ways to create an owned `String`: 1. **Using `String::from()`:** This is a common method to convert a string literal (which is a `&str`) or other types that implement the `Into<String>` trait into an owned `String`. ```rust // fn main() { let msg: String = String::from("Hello Rust"); // } ``` 2. **Using the `.to_string()` method:** Many types, including string literals (`&str`), implement the `ToString` trait, which provides a `.to_string()` method to create a `String`. ```rust // fn main() { let msg: String = "Hello Rust".to_string(); // } ``` **Getting the Length of a `String`** You can determine the length of a `String` using the `.len()` method. It's important to note that `.len()` returns the size of the string in bytes, not necessarily the number of characters. This is because Rust strings are UTF-8 encoded, and a single character can take up multiple bytes. ```rust // fn main() { let msg: String = String::from("Hello Rust"); let length: usize = msg.len(); // length will be 10 (number of bytes) // println!("Length: {}", length); // } ``` The type `usize` is an unsigned integer type. Its size (e.g., 32-bit or 64-bit) depends on the architecture of the computer your program is compiled for, making it suitable for indexing and representing memory sizes. ## Working with `&str` (String Slices): Creation String slices (`&str`) are references to string data. Here's how you can obtain them: 1. **From a String Literal:** As mentioned, string literals are inherently string slices. ```rust // fn main() { let s: &str = "Hello World"; // s is a &'static str // } ``` 2. **By Referencing a `String`:** You can create a `&str` that refers to the entire content of an existing `String`. ```rust // fn main() { let msg: String = String::from("Hello Rust"); let s: &str = &msg; // s is a slice referencing all of msg // } ``` Here, `s` borrows the data owned by `msg`. 3. **By Slicing a `String`:** You can create a `&str` that refers to a specific portion (a "slice") of a `String` using range syntax. ```rust // fn main() { let msg: String = String::from("Hello Rust"); // Create a slice containing "Hello" (indices 0 up to, but not including, 5) let s: &str = &msg[0..5]; println!("s = {}", s); // } ``` The output will be: ``` s = Hello ``` Be cautious when slicing: if the range boundaries do not fall on valid UTF-8 character boundaries, your program will panic. ## Bridging the Gap: Conversions Between `String` and `&str` Rust provides seamless ways to convert between these two string types. * **Converting `&str` to `String`:** If you have a string slice (`&str`) and need an owned `String` (perhaps to modify it or return it from a function), you can use `.to_string()` or `String::from()`: ```rust // fn main() { let s_slice: &str = "Hello World"; let owned_string_v1: String = s_slice.to_string(); let owned_string_v2: String = String::from(s_slice); // } ``` * **Converting `&String` to `&str` (Deref Coercion):** This is a powerful and often implicit conversion. Rust can automatically convert a reference to a `String` (i.e., `&String`) into a string slice (`&str`). This is enabled by a feature called "deref coercion." The `String` type implements the `Deref` trait, allowing it to be treated like a `&str` in many contexts, particularly when passing arguments to functions. Consider a function designed to print any string data it receives: ```rust fn print_message(s: &str) { // Function accepts a string slice println!("{}", s); } fn main() { // Example with a String let msg_string: String = String::from("Hello from String"); print_message(&msg_string); // Rust automatically coerces &msg_string (a &String) to &str // Example with a string literal (&str) let s_literal: &str = "Hello from literal"; print_message(s_literal); // s_literal is already a &str } ``` If `print_message` were defined as `fn print_message(s: &String)`, attempting to pass `s_literal` (a `&str`) directly would result in a compile-time error. By accepting `&str`, the function becomes more flexible and idiomatic, as it can work with any string data without needing to take ownership. ## Modifying and Constructing Strings While `&str` is immutable, `String` is designed for modification and dynamic construction. 1. **Appending a `&str` to a `String`:** You can append a string slice (`&str`) to a mutable `String` using the `+=` operator or the `push_str` method. The `+=` operator uses `push_str` behind the scenes. ```rust // fn main() { let mut msg: String = String::from("Hello Rust"); msg += " World"; // Appends the string slice " World" // Alternatively: msg.push_str(" World"); println!("{}", msg); // Output: Hello Rust World // } ``` Note that `msg` must be declared as `mut` (mutable) to allow modification. 2. **String Interpolation (Formatting with `format!`):** When you need to construct a new `String` from various pieces of data (other strings, numbers, etc.), the `format!` macro is the preferred approach over manual concatenation with `+` or `+=`. The `+` operator for `String` concatenation can be less efficient and harder to read for complex cases. ```rust // fn main() { let name = "Rust"; // name is a &str let version = 1.76; // version is an f64 let emoji = "🦀"; // emoji is a &str // Desired string: "Learning Rust version 1.76 is fun! 🦀" // Using format! let s: String = format!("Learning {} version {} is fun! {}", name, version, emoji); println!("{}", s); // Output: Learning Rust version 1.76 is fun! 🦀 // } ``` The `format!` macro works similarly to `println!` but, instead of printing to the console, it returns a new, heap-allocated `String`. ## Key Considerations and Best Practices * **Choosing Between `String` and `&str`:** * **Use `String`** when you need ownership of the string data (e.g., returning a string from a function, storing it in a struct that outlives the current scope) or when you need to modify the string. * **Use `&str`** for read-only views of string data. It's especially good for function parameters that don't need to take ownership or mutate the string, as this allows the function to accept both `String` references and string literals. * **Leverage Deref Coercion:** Remember that Rust's deref coercion (e.g., `&String` to `&str`) makes APIs more ergonomic. Design your functions to accept `&str` when read-only access is sufficient. * **Understanding String Literals:** String literals (e.g., `"hello"`) are always of type `&'static str`. This means they are string slices that are guaranteed to live for the entire duration of your program, as they are typically embedded directly into the compiled binary. ## Practical Use Cases Recap This lesson covered several common scenarios involving `String` and `&str`: * Creating empty `String` objects or `String` objects pre-filled with data from literals. * Determining the byte length of a `String` using `.len()`. * Creating string slices (`&str`) from existing `String`s (either the whole string or a portion) or directly from string literals. * Passing string data to functions, highlighting the flexibility of using `&str` as a parameter type thanks to deref coercion. * Modifying a `String` by appending additional string data (slices). * Constructing new, formatted `String`s from various components using the `format!` macro. By mastering the distinctions and interactions between `String` and `&str`, you'll be well-equipped to handle text data effectively and idiomatically in your Rust programs. This foundational knowledge is crucial for building robust and performant applications.
String
vs. &str
Welcome to this comprehensive guide on handling text in Rust. Understanding how Rust manages strings is fundamental to writing efficient and safe code. Rust offers two primary string types: String
, an owned, heap-allocated string, and &str
(pronounced "string slice"), which is a reference to string data. This lesson will delve into their characteristics, use cases, creation, manipulation, and how they interact.
String
and &str
At the heart of Rust's string handling are String
and &str
. Let's explore each one.
String
(with a capital 'S')
Nature: String
is an owned data type. This means when you have a String
, your variable directly owns the string data, which is stored on the heap. This allows the string to be growable and modifiable.
When to use String
:
Ownership is required: When the string data needs to persist longer than the current function call or scope, or if it needs to be returned from a function, String
is the appropriate choice.
Mutability is needed: If you intend to modify the string (e.g., append characters, clear its contents, insert substrings), you need a String
.
&str
(String Slice)
Nature: &str
is a borrowed type, specifically a "slice." It's an immutable reference to a sequence of UTF-8 encoded bytes. Think of it as a "view" into string data that could be owned by a String
, or it could be a string literal embedded directly in your program's binary.
String Literals: When you write let message = "hello";
, the type of message
is &'static str
. The &
indicates it's a reference, str
indicates it's a string slice, and 'static
is a lifetime annotation signifying that this string data is valid for the entire duration of the program.
When to use &str
:
Read-only access: When you only need to read or inspect string data without modifying it.
Working with string literals: String literals are inherently &str
.
Function parameters (flexibility): It's often preferred for function parameters when the function only needs to read the string. This is because &str
can accept both string literals and references to String
s due to a feature called deref coercion, making your functions more versatile.
String
: Creation and Basic OperationsLet's look at how to create and perform basic operations on String
types.
Creating a String
There are several idiomatic ways to create an owned String
:
Using String::from()
: This is a common method to convert a string literal (which is a &str
) or other types that implement the Into<String>
trait into an owned String
.
Using the .to_string()
method: Many types, including string literals (&str
), implement the ToString
trait, which provides a .to_string()
method to create a String
.
Getting the Length of a String
You can determine the length of a String
using the .len()
method. It's important to note that .len()
returns the size of the string in bytes, not necessarily the number of characters. This is because Rust strings are UTF-8 encoded, and a single character can take up multiple bytes.
The type usize
is an unsigned integer type. Its size (e.g., 32-bit or 64-bit) depends on the architecture of the computer your program is compiled for, making it suitable for indexing and representing memory sizes.
&str
(String Slices): CreationString slices (&str
) are references to string data. Here's how you can obtain them:
From a String Literal: As mentioned, string literals are inherently string slices.
By Referencing a String
: You can create a &str
that refers to the entire content of an existing String
.
Here, s
borrows the data owned by msg
.
By Slicing a String
: You can create a &str
that refers to a specific portion (a "slice") of a String
using range syntax.
The output will be:
Be cautious when slicing: if the range boundaries do not fall on valid UTF-8 character boundaries, your program will panic.
String
and &str
Rust provides seamless ways to convert between these two string types.
Converting &str
to String
:
If you have a string slice (&str
) and need an owned String
(perhaps to modify it or return it from a function), you can use .to_string()
or String::from()
:
Converting &String
to &str
(Deref Coercion):
This is a powerful and often implicit conversion. Rust can automatically convert a reference to a String
(i.e., &String
) into a string slice (&str
). This is enabled by a feature called "deref coercion." The String
type implements the Deref
trait, allowing it to be treated like a &str
in many contexts, particularly when passing arguments to functions.
Consider a function designed to print any string data it receives:
If print_message
were defined as fn print_message(s: &String)
, attempting to pass s_literal
(a &str
) directly would result in a compile-time error. By accepting &str
, the function becomes more flexible and idiomatic, as it can work with any string data without needing to take ownership.
While &str
is immutable, String
is designed for modification and dynamic construction.
Appending a &str
to a String
:
You can append a string slice (&str
) to a mutable String
using the +=
operator or the push_str
method. The +=
operator uses push_str
behind the scenes.
Note that msg
must be declared as mut
(mutable) to allow modification.
String Interpolation (Formatting with format!
):
When you need to construct a new String
from various pieces of data (other strings, numbers, etc.), the format!
macro is the preferred approach over manual concatenation with +
or +=
. The +
operator for String
concatenation can be less efficient and harder to read for complex cases.
The format!
macro works similarly to println!
but, instead of printing to the console, it returns a new, heap-allocated String
.
Choosing Between String
and &str
:
Use String
when you need ownership of the string data (e.g., returning a string from a function, storing it in a struct that outlives the current scope) or when you need to modify the string.
Use &str
for read-only views of string data. It's especially good for function parameters that don't need to take ownership or mutate the string, as this allows the function to accept both String
references and string literals.
Leverage Deref Coercion: Remember that Rust's deref coercion (e.g., &String
to &str
) makes APIs more ergonomic. Design your functions to accept &str
when read-only access is sufficient.
Understanding String Literals: String literals (e.g., "hello"
) are always of type &'static str
. This means they are string slices that are guaranteed to live for the entire duration of your program, as they are typically embedded directly into the compiled binary.
This lesson covered several common scenarios involving String
and &str
:
Creating empty String
objects or String
objects pre-filled with data from literals.
Determining the byte length of a String
using .len()
.
Creating string slices (&str
) from existing String
s (either the whole string or a portion) or directly from string literals.
Passing string data to functions, highlighting the flexibility of using &str
as a parameter type thanks to deref coercion.
Modifying a String
by appending additional string data (slices).
Constructing new, formatted String
s from various components using the format!
macro.
By mastering the distinctions and interactions between String
and &str
, you'll be well-equipped to handle text data effectively and idiomatically in your Rust programs. This foundational knowledge is crucial for building robust and performant applications.
An essential guide to Mastering Strings in Rust: `String` vs. `&str` - Understand Rust's core string types: the owned, heap-allocated `String` for mutable text, and `&str`, an immutable reference to string data. Learn to create, manipulate (slice, append, format), convert between them, and apply best practices for choosing the right type.
Previous lesson
Previous
Next lesson
Next
Give us feedback
Course Overview
About the course
Introduction to the Rust programming language
Rust variables and functions
Scalar types, arrays, strings, enum, structs, vectors, and hash maps in Rust
Rust control flows: If / else, if let and let else, loop, match
Rust ownership, including borrow and references
Rust error handling
Rust Modules
Rust Traits
Last updated on July 11, 2025
Rust Developer
Rust Programming BasicsDuration: 6min
Duration: 18min
Duration: 47min
Duration: 15min
Duration: 19min
Duration: 8min
Duration: 12min
Duration: 46min
Duration: 14min
Course Overview
About the course
Introduction to the Rust programming language
Rust variables and functions
Scalar types, arrays, strings, enum, structs, vectors, and hash maps in Rust
Rust control flows: If / else, if let and let else, loop, match
Rust ownership, including borrow and references
Rust error handling
Rust Modules
Rust Traits
Last updated on July 11, 2025