Rust Tutorial: Mastering Strings with Real Examples
Strings are everywhere in programming, and Rust has its own way of handling them—with a focus on safety and performance. In this guide, you’ll get a clear overview of how strings work in Rust, including the difference between String and &str, plus three real-world examples of string manipulation. By the end, you’ll be writing, modifying, and thinking about strings the Rust way. What You’ll Learn The two main string types: String and &str How Rust handles UTF-8 encoding The role of ownership and borrowing with strings Three practical string manipulation examples Let’s jump in. Strings in Rust: The Basics Rust has two main types for working with strings: 1. &str: The String Slice What it is: A reference to a string—immutable and usually from a string literal. Where it lives: Stored on the stack as a pointer and length. Use it when: You just need to read or pass around string data without changing it. Example: let greeting: &str = "Hello, world!"; println!("Length of '{}': {}", greeting, greeting.len()); 2. String: The Owned String What it is: A growable, heap-allocated string that owns its data. How to create it: Use String::from() or call .to_string() on a &str. Use it when: You need to modify the contents or build a string dynamically. Example: let mut message = String::from("Hello"); message.push_str(", world!"); println!("Modified string: {}", message); Quick Comparison Feature &str String Ownership Borrowed Owned Mutability Immutable Mutable Memory Stack (reference) Heap (data) Use Case Read-only access Dynamic manipulation A Note on UTF-8 Rust strings are UTF-8 encoded, which means they support characters from all languages and even emojis. But here’s the catch: .len() gives the byte length, not the character count. To count characters, use .chars().count(). Example: let s = "olá"; println!("Bytes: {}, Characters: {}", s.len(), s.chars().count()); // Output: Bytes: 5, Characters: 3 Real-World String Manipulation Examples Time to get hands-on. Here are three practical ways to manipulate strings in Rust. Example 1: Reversing a String Goal: Reverse the characters in a string. pub fn reverse_string(input: &str) -> String { input.chars().rev().collect() } fn main() { let s = "Hello, Rust!"; let reversed = reverse_string(s); println!("Original: '{}', Reversed: '{}'", s, reversed); } What’s happening: We borrow the input as a &str. Use .chars() to get an iterator over characters (not bytes). Reverse the iterator and collect it into a new String. ✅ Handles Unicode characters properly. Example 2: Appending Text to a String Goal: Add a suffix to a String using a mutable reference. pub fn add_suffix(s: &mut String, suffix: &str) { s.push_str(suffix); } fn main() { let mut name = String::from("Alice"); println!("Before: {}", name); add_suffix(&mut name, " in Wonderland"); println!("After: {}", name); } Why it works: &mut String gives us exclusive, mutable access. push_str() adds the &str to the end of the string. No need to return anything—the original String is updated. Example 3: Replacing Parts of a String Goal: Replace all instances of one word with another. pub fn replace_text(input: &str, from: &str, to: &str) -> String { input.replace(from, to) } fn main() { let sentence = "I like to code in Rust, Rust is great!"; let new_sentence = replace_text(sentence, "Rust", "Python"); println!("Original: '{}'", sentence); println!("Replaced: '{}'", new_sentence); } Behind the scenes: replace() works on a &str and returns a new String. Doesn’t modify the original—just returns the updated version. Wrap-Up: Full Program Example Here’s everything combined in a complete Rust program: pub fn reverse_string(input: &str) -> String { input.chars().rev().collect() } pub fn add_suffix(s: &mut String, suffix: &str) { s.push_str(suffix); } pub fn replace_text(input: &str, from: &str, to: &str) -> String { input.replace(from, to) } #[cfg(test)] mod tests { use super::*; #[test] fn test_reverse_string() { assert_eq!(reverse_string("Hello"), "olleH"); } #[test] fn test_add_suffix() { let mut s = String::from("Hello"); add_suffix(&mut s, "!"); assert_eq!(s, "Hello!"); } #[test] fn test_replace_text() { assert_eq!(replace_text("cat cat", "cat", "dog"), "dog dog"); } } fn main() { // Reverse let s1 = "Rust"; println!("Reversed: '{}'", reverse_string(s1)); // Append let mut s2 = String::from("Rust"); add_suffix(&mut s2, " is fun"); println!("Appended: '{}'", s2); // Replace let s3 = "Rust Rust"; println!("Replaced: '{}'", replace_text(s3, "Rust", "Code")); } Run it with: cargo

Strings are everywhere in programming, and Rust has its own way of handling them—with a focus on safety and performance. In this guide, you’ll get a clear overview of how strings work in Rust, including the difference between String
and &str
, plus three real-world examples of string manipulation.
By the end, you’ll be writing, modifying, and thinking about strings the Rust way.
What You’ll Learn
- The two main string types:
String
and&str
- How Rust handles UTF-8 encoding
- The role of ownership and borrowing with strings
- Three practical string manipulation examples
Let’s jump in.
Strings in Rust: The Basics
Rust has two main types for working with strings:
1. &str
: The String Slice
- What it is: A reference to a string—immutable and usually from a string literal.
- Where it lives: Stored on the stack as a pointer and length.
- Use it when: You just need to read or pass around string data without changing it.
Example:
let greeting: &str = "Hello, world!";
println!("Length of '{}': {}", greeting, greeting.len());
2. String
: The Owned String
- What it is: A growable, heap-allocated string that owns its data.
-
How to create it: Use
String::from()
or call.to_string()
on a&str
. - Use it when: You need to modify the contents or build a string dynamically.
Example:
let mut message = String::from("Hello");
message.push_str(", world!");
println!("Modified string: {}", message);
Quick Comparison
Feature | &str |
String |
---|---|---|
Ownership | Borrowed | Owned |
Mutability | Immutable | Mutable |
Memory | Stack (reference) | Heap (data) |
Use Case | Read-only access | Dynamic manipulation |
A Note on UTF-8
Rust strings are UTF-8 encoded, which means they support characters from all languages and even emojis. But here’s the catch:
-
.len()
gives the byte length, not the character count. - To count characters, use
.chars().count()
.
Example:
let s = "olá";
println!("Bytes: {}, Characters: {}", s.len(), s.chars().count());
// Output: Bytes: 5, Characters: 3
Real-World String Manipulation Examples
Time to get hands-on. Here are three practical ways to manipulate strings in Rust.
Example 1: Reversing a String
Goal: Reverse the characters in a string.
pub fn reverse_string(input: &str) -> String {
input.chars().rev().collect()
}
fn main() {
let s = "Hello, Rust!";
let reversed = reverse_string(s);
println!("Original: '{}', Reversed: '{}'", s, reversed);
}
What’s happening:
- We borrow the input as a
&str
. - Use
.chars()
to get an iterator over characters (not bytes). - Reverse the iterator and collect it into a new
String
.
✅ Handles Unicode characters properly.
Example 2: Appending Text to a String
Goal: Add a suffix to a String
using a mutable reference.
pub fn add_suffix(s: &mut String, suffix: &str) {
s.push_str(suffix);
}
fn main() {
let mut name = String::from("Alice");
println!("Before: {}", name);
add_suffix(&mut name, " in Wonderland");
println!("After: {}", name);
}
Why it works:
-
&mut String
gives us exclusive, mutable access. -
push_str()
adds the&str
to the end of the string. - No need to return anything—the original
String
is updated.
Example 3: Replacing Parts of a String
Goal: Replace all instances of one word with another.
pub fn replace_text(input: &str, from: &str, to: &str) -> String {
input.replace(from, to)
}
fn main() {
let sentence = "I like to code in Rust, Rust is great!";
let new_sentence = replace_text(sentence, "Rust", "Python");
println!("Original: '{}'", sentence);
println!("Replaced: '{}'", new_sentence);
}
Behind the scenes:
-
replace()
works on a&str
and returns a newString
. - Doesn’t modify the original—just returns the updated version.
Wrap-Up: Full Program Example
Here’s everything combined in a complete Rust program:
pub fn reverse_string(input: &str) -> String {
input.chars().rev().collect()
}
pub fn add_suffix(s: &mut String, suffix: &str) {
s.push_str(suffix);
}
pub fn replace_text(input: &str, from: &str, to: &str) -> String {
input.replace(from, to)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_reverse_string() {
assert_eq!(reverse_string("Hello"), "olleH");
}
#[test]
fn test_add_suffix() {
let mut s = String::from("Hello");
add_suffix(&mut s, "!");
assert_eq!(s, "Hello!");
}
#[test]
fn test_replace_text() {
assert_eq!(replace_text("cat cat", "cat", "dog"), "dog dog");
}
}
fn main() {
// Reverse
let s1 = "Rust";
println!("Reversed: '{}'", reverse_string(s1));
// Append
let mut s2 = String::from("Rust");
add_suffix(&mut s2, " is fun");
println!("Appended: '{}'", s2);
// Replace
let s3 = "Rust Rust";
println!("Replaced: '{}'", replace_text(s3, "Rust", "Code"));
}
Run it with:
cargo run
Output:
Reversed: 'tsuR'
Appended: 'Rust is fun'
Replaced: 'Code Code'
Final Thoughts
- Use
&str
when reading or passing strings. - Use
String
when you need ownership or want to modify the data. - Always think in terms of Rust’s borrowing and ownership model.
- Remember
.len()
gives byte length—use.chars()
when working with Unicode.
You’ve got the tools now. Want a challenge? Try writing a function to capitalize each word or trim whitespace. Rust gives you the power—use it well.