Question
In Rust, why are there both String and str types? What is the difference between them, when should each one be used, and is either type being deprecated?
Short Answer
By the end of this page, you will understand how Rust represents text with both String and str, why they are different types, and how ownership and borrowing affect string handling. You will also learn when to use String, when to use &str, and why neither type is being deprecated.
Concept
Rust has multiple string-related types because they serve different purposes in Rust's ownership system.
Stringis an owned, growable UTF-8 text buffer.stris a string slice type, usually used as&str, which is a borrowed view into UTF-8 text.
Why Rust needs both
In Rust, ownership matters. Sometimes your code needs to own text so it can store it, modify it, or return it from a function. Other times, your code only needs to read some text temporarily without taking ownership.
Having separate types makes these cases explicit:
- Use
Stringwhen your code owns the text. - Use
&strwhen your code just borrows text.
This helps Rust prevent memory errors and unnecessary copying.
String
String stores text on the heap and owns that memory. Because it owns the data, it can:
- grow or shrink
- be modified
- be moved between variables
- be returned from functions as owned data
Example:
let mut name = String::from("Rust");
name.push_str(" language");
str
str is not usually used by itself. You normally see it as &str.
A &str is a reference to UTF-8 text stored somewhere else. That text might come from:
- a string literal
- part of a
String - part of another
&str
Example:
let language: &str = "Rust";
String literals like "Rust" have type &'static str, meaning they are stored in the program binary and live for the whole program.
Important relationship
A String can be borrowed as &str:
let s = String::from("hello");
let slice: &str = &s;
This is why many Rust APIs accept &str: they can work with both string literals and String values without forcing ownership.
Is one deprecated?
No. Neither String nor str is deprecated.
They are both fundamental parts of Rust:
Stringis the standard owned string type.stris the fundamental string slice type.
Both are actively used throughout the language and standard library.
Mental Model
Think of String as a book you own, and &str as a page reference or excerpt from a book.
- If you own the whole book and can add or remove pages, that is like
String. - If you are only pointing to some text inside a book owned elsewhere, that is like
&str.
Another way to see it:
String= the actual storage container&str= a window looking into text
The window does not own the text. It only lets you read it while the original text still exists.
Syntax and Examples
Core syntax
let owned: String = String::from("hello");
let borrowed: &str = "hello";
Create a String
let a = String::from("hello");
let b = "hello".to_string();
Both create owned strings.
Use a string slice
let name: &str = "Ada";
This borrows a string literal.
Borrow a String as &str
let message = String::from("welcome");
let view: &str = &message;
Here, view does not own the text. It just points to the contents of message.
Function parameters
A common beginner-friendly pattern is to accept &str when you only need to read text:
fn greet(name: &str) {
println!("Hello, {}!", name);
}
fn main() {
let a = "Rust";
let b = String::from("Ferris");
greet(a);
greet(&b);
}
This is flexible because both string literals and String values can be used.
When you need ownership
fn make_message(name: &str) -> String {
format!("Hello, {}!", name)
}
This function returns a new owned String because it creates new text.
Slicing text
let word = String::from("hello");
let part: &str = &word[0..2];
println!("{}", part);
This creates a string slice referencing part of the string.
Be careful: Rust strings are UTF-8, so not every byte range is a valid character boundary.
Step by Step Execution
Consider this example:
fn print_length(text: &str) {
println!("{} has length {}", text, text.len());
}
fn main() {
let s = String::from("hello");
let slice = &s;
print_length(slice);
}
Step by step:
String::from("hello")creates an ownedStringon the heap.sowns that string data.let slice = &s;borrowssas&str.slicedoes not copy the text. It only points to the same text.print_length(slice)passes that borrowed view into the function.- Inside
print_length, the function can read the text but does not own it. - When the function ends,
sliceis still valid becausesstill exists. - At the end of
main,sis dropped, and its memory is freed.
What if the function took String instead?
fn print_length(text: String) {
println!("{} has length {}", text, text.len());
}
Then calling print_length(s) would move ownership into the function. After that, s could no longer be used unless cloned. That is why &str is often the better parameter type for read-only text.
Real World Use Cases
Accepting user input
When reading input from a user, you often store it in a String because the program owns that text and may modify it.
let mut input = String::new();
API and function parameters
Functions that only read text commonly accept &str:
fn is_valid_email(email: &str) -> bool {
email.contains('@')
}
This works with both literals and String values.
Building output text
When constructing messages, file contents, SQL queries, or JSON snippets, you usually return or build a String.
Parsing and processing text
Parsers often receive &str because they inspect text without needing to own it.
Configuration values
A config loader may temporarily borrow text as &str while validating, then store selected values as String if they must live independently.
Real Codebase Usage
In real Rust codebases, developers follow a few common patterns.
Prefer &str for read-only parameters
fn log_message(message: &str) {
println!("LOG: {}", message);
}
This avoids unnecessary allocations and makes the function easier to call.
Use String for stored data
Struct fields often use String when the struct must own the text:
struct User {
username: String,
email: String,
}
Convert at boundaries
A common pattern is:
- accept
&str - validate or inspect it
- convert to
Stringonly if ownership is needed
fn create_user(username: &str) -> User {
User {
username: username.to_string(),
email: String::from("unknown@example.com"),
}
}
Guard clauses and validation
fn save_name(name: &str) -> Result<String, String> {
if name.trim().is_empty() {
return Err(String::from("name cannot be empty"));
}
Ok(name.trim().to_string())
}
Here &str is used for input, and String is used for owned output and error messages.
Avoid cloning unless necessary
Developers try to borrow with &str instead of creating a new String unless they really need ownership.
Common Mistakes
Mistake 1: Thinking str and &str are the same thing
str is the slice type itself, but you almost always work with it through a reference: &str.
Broken idea:
let text: str = "hello";
Correct:
let text: &str = "hello";
Mistake 2: Using String for every function parameter
Broken:
fn greet(name: String) {
println!("Hello, {}", name);
}
This forces callers to give ownership.
Better:
fn greet(name: &str) {
println!("Hello, {}", name);
}
Mistake 3: Forgetting that String can be borrowed as &str
let name = String::from("Rust");
greet(&name);
You usually do not need to convert a String into another String just to pass it.
Mistake 4: Slicing UTF-8 strings at invalid byte boundaries
Broken:
let s = "é";
let part = &s[0..1];
This panics because é uses more than one byte in UTF-8.
Safer approaches include iterating over .chars() when you need character-based logic.
Mistake 5: Returning &str when the data is created inside the function
Broken:
fn make_text() -> &str {
let s = String::from("hello");
&s
}
This returns a reference to data that will be dropped.
Correct:
fn make_text() -> String {
String::from("hello")
}
Comparisons
| Concept | String | &str |
|---|---|---|
| Ownership | Owns the text | Borrows the text |
| Size | Growable | Fixed view into existing text |
| Mutability | Can be modified if variable is mut | Cannot modify underlying text through &str |
| Allocation | Usually heap-allocated | Does not allocate by itself |
| Common use | Store, build, return text | Read-only parameters and views |
| Typical function argument | Less flexible | More flexible |
String vs &str in function design
| Function signature | When to use it |
|---|---|
fn f(text: &str) | When you only need to read text |
fn f(text: String) | When the function must take ownership |
fn f() -> String | When the function creates and returns new text |
String vs string literal
A string literal like "hello" is not a String.
- Literal type:
&'static str - Owned heap string:
String
You can convert a literal into a String when needed:
let s = "hello".to_string();
Cheat Sheet
Quick rules
String= owned, growable UTF-8 textstr= string slice type&str= borrowed view into UTF-8 text- String literals are usually
&'static str - Neither
Stringnorstris deprecated
Most common choices
fn read_only(text: &str) {}
fn creates_text() -> String { String::from("hi") }
Convert between them
let s: String = String::from("hello");
let slice: &str = &s;
let owned: String = slice.to_string();
When to use what
- Use
&strfor function inputs when you only read text - Use
Stringfor stored fields, owned results, and editable text
Common constructors
String::from("hello")
"hello".to_string()
Important note about slicing
Rust strings are UTF-8. Byte indices must land on valid character boundaries.
Good default design advice
- Accept
&str - Return
Stringwhen producing new text - Store
Stringwhen ownership is required
FAQ
Why does Rust have both String and str?
Because Rust separates owned data from borrowed data. String owns text, while &str borrows text.
Should I use String or &str for function parameters?
Usually &str, unless the function must take ownership of the text.
Is str ever used without &?
Rarely in everyday Rust code. Most of the time you use &str.
Are string literals String values?
No. String literals are &'static str.
Is String just a wrapper around str?
You can think of String as owned string storage that can be borrowed as &str, but they are distinct types with different roles.
Is either String or str deprecated in Rust?
No. Both are core parts of Rust and are widely used.
Why do many Rust APIs accept &str?
Because it is more flexible and avoids forcing allocation or ownership transfer.
Can I modify a &str?
Not directly. If you need editable owned text, use String.
Mini Project
Description
Build a small Rust program that manages a display name. This project demonstrates when to accept &str, when to store String, and how to convert borrowed text into owned text only when needed.
Goal
Create a program that validates a name from borrowed input and stores it as owned text in a struct.
Requirements
[
"Create a struct that stores a user's display name as a String.",
"Write a function that accepts &str input and rejects empty or whitespace-only names.",
"Trim valid input and store the cleaned value as a String.",
"Print the saved display name to confirm the result."
]
Keep learning
Related questions
Absolute Values in Rust: Using the abs() Method
Learn how to calculate absolute values in modern Rust with abs(), handle signed integer overflow, and choose safer alternatives.
Accepting Iterators in Rust Functions with impl Iterator
Learn how to write Rust functions that accept HashMap iterators using generic bounds or impl Iterator, avoiding trait object and lifetime errors.
Access Enum Variant Values in Rust with Pattern Matching
Learn how to extract and print values stored in Rust enum variants using match and if let pattern matching, with practical examples.