Question
I want to convert a Rust String into a &str. More specifically, I need to understand whether it can be converted into a string slice with the 'static lifetime, written as &'static str.
For example, I am trying to work with owned string data and then use it where a static string slice is expected. What is the correct way to do this in Rust, and what are the limitations?
let s: String = String::from("hello");
// How can this become &str?
// Can it become &'static str?
Short Answer
By the end of this page, you will understand the difference between String, &str, and &'static str in Rust, why a normal String cannot usually become &'static str, and what safe alternatives to use. You will also see when borrowing is enough, when leaking memory is technically possible, and how real Rust code usually handles this situation.
Concept
String and &str are closely related in Rust, but they are not the same type.
Stringis an owned, growable string stored on the heap.&stris a borrowed view into string data.&'static stris a borrowed string slice that is guaranteed to live for the entire duration of the program.
A String can easily be borrowed as a &str because the String owns the text data.
let s = String::from("hello");
let slice: &str = &s;
This works because slice only borrows from s, and s is still alive.
The important part is the lifetime. A borrowed &str can only live as long as the data it points to. Since a normal String is dropped when it goes out of scope, any &str borrowed from it also becomes invalid at that point.
That is why you cannot normally turn a runtime-created String into &'static str. A 'static reference means the data must remain valid for the whole program, but an ordinary String does not.
The most common source of &'static str is a string literal:
let name: &'static str = "hello";
String literals are baked into the compiled program, so they naturally have the 'static lifetime.
Why this matters:
- Rust uses lifetimes to prevent dangling references.
- Understanding ownership and borrowing helps you choose the right type in APIs.
- Many beginner errors in Rust come from expecting borrowed data to outlive its owner.
In practice, if you have a String, the usual answer is:
- borrow it as
&strwhen you only need a temporary view - keep it as
Stringif ownership is needed - use
&'static stronly when the data is truly static or intentionally leaked
Mental Model
Think of a String as a notebook that you own.
A &str is like letting someone read a page from your notebook. They can read it only while your notebook still exists.
A &'static str is like text carved into stone in a public square. It stays there for the entire lifetime of the program.
So:
String= owned notebook&str= borrowed page from the notebook&'static str= permanent inscription
If you create a String at runtime, it is not automatically permanent. Borrowing from it does not make it permanent either. The borrowed slice is still tied to the owner.
Syntax and Examples
Borrowing String as &str
This is the normal conversion:
let s = String::from("hello");
let slice1: &str = &s;
let slice2: &str = s.as_str();
println!("{} {}", slice1, slice2);
Both &s and s.as_str() give you a &str view into the string.
Why this is not &'static str
fn make_str() -> &str {
let s = String::from("hello");
s.as_str()
}
This does not compile, because s is dropped when the function ends. Returning a reference to it would create a dangling reference.
Returning an owned String instead
If the string is created at runtime, return String:
fn make_string() -> String {
String::from("hello")
}
Using a string literal for &'static str
fn message() -> &'static str {
"hello"
}
This works because string literals are static.
Intentionally leaking a String
Rust does allow you to convert a String into &'static str by leaking it:
fn make_static(s: String) -> &'static str {
Box::leak(s.into_boxed_str())
}
fn main() {
let s = String::from("hello");
let static_str: &'static str = make_static(s);
println!("{}", static_str);
}
This works, but the memory is never freed. It should only be used in special cases, such as initializing data that truly lives for the whole program.
Prefer shared ownership when appropriate
Sometimes you want long-lived string data, but not necessarily &'static str. In real programs, Arc<str> or String is often a better fit.
use std::sync::Arc;
let text: Arc<str> = Arc::from("hello".to_string());
println!("{}", text);
Step by Step Execution
Consider this example:
fn main() {
let s = String::from("hello");
let slice = s.as_str();
println!("{}", slice);
}
Step by step:
-
let s = String::from("hello");- A
Stringis created on the heap. - The variable
sowns that heap data.
- A
-
let slice = s.as_str();slicebecomes a&strborrowing the contents ofs.- No new string is allocated.
slicepoints to the same text data owned bys.
-
println!("{}", slice);- This is valid because
sstill exists.
- This is valid because
-
End of
mainslicegoes out of scope.sis dropped.- The heap memory for the string is freed.
Now compare that with this invalid pattern:
fn bad() -> &str {
let s = String::from("hello");
s.as_str()
}
What happens here:
sis created insidebad.s.as_str()creates a borrowed&str.- The function tries to return that borrowed reference.
- But
sis dropped immediately when the function ends. - The returned reference would point to freed memory.
- Rust rejects this at compile time.
That is exactly what Rust's lifetime system is designed to prevent.
Real World Use Cases
Borrow a String as &str
This is common when calling functions that only need read-only access:
fn print_name(name: &str) {
println!("{}", name);
}
let name = String::from("Ava");
print_name(&name);
Accept either String or &str
Many APIs take &str so callers can pass both string literals and borrowed String data.
fn is_empty_text(text: &str) -> bool {
text.trim().is_empty()
}
Use &'static str for fixed messages
Useful for constant labels, error messages, and compile-time text:
fn status_label(code: u32) -> &'static str {
match code {
200 => "OK",
404 => "Not Found",
_ => "Unknown",
}
}
Use owned strings for dynamic content
If content depends on user input, configuration, or database values, use String:
fn welcome(name: &str) -> String {
format!("Welcome, {}!", name)
}
Leak only for program-long registries
In rare cases, applications build global lookup tables once and keep them forever. In those cases, leaking may be acceptable, but it should be a deliberate design choice.
Real Codebase Usage
In real Rust projects, developers usually avoid forcing dynamic strings into &'static str.
Common patterns include:
1. Accept &str in function parameters
This is flexible and ergonomic.
fn log_message(msg: &str) {
println!("LOG: {}", msg);
}
Callers can pass:
- string literals
&String- string slices
2. Return String for computed text
If the function builds text dynamically, return ownership.
fn build_path(user: &str) -> String {
format!("/home/{}", user)
}
3. Use &'static str for fixed enums or constants
const APP_NAME: &str = "my_app";
4. Use guard clauses with borrowed strings
fn validate(name: &str) -> Result<(), &'static str> {
if name.trim().is_empty() {
return Err("name cannot be empty");
}
Ok(())
}
Static error messages are a good fit for &'static str because they are fixed literals.
5. Prefer owned or shared types for stored dynamic data
For application state, config values, HTTP payloads, and parsed file contents, developers usually store:
StringBox<str>Arc<str>Cow<'a, str>when borrowing-or-owning flexibility is useful
6. Avoid unnecessary lifetime constraints
If you write an API requiring &'static str, you make it impossible to pass ordinary runtime strings. Real codebases often loosen this to &str or impl AsRef<str> unless true static data is required.
Common Mistakes
Mistake 1: Assuming &str always means 'static
&str does not automatically mean &'static str.
let s = String::from("hello");
let slice: &str = &s; // valid
This slice only lives as long as s does.
Mistake 2: Returning a borrowed slice from a local String
Broken code:
fn bad() -> &str {
let s = String::from("hello");
&s
}
Why it fails:
sis dropped at the end of the function- the returned reference would dangle
Fix: return String instead.
fn good() -> String {
String::from("hello")
}
Mistake 3: Using &'static str when String is the right type
Broken design:
fn username() -> &'static str {
let name = String::from("user123");
Box::leak(name.into_boxed_str())
}
This compiles, but leaks memory every time it runs.
Fix: return String unless you truly need permanent storage.
Mistake 4: Over-constraining function parameters
Too strict:
fn greet(name: &'static str) {
println!("Hello, {}", name);
}
This prevents passing many useful values.
Better:
fn greet(name: &str) {
println!("Hello, {}", name);
}
Mistake 5: Thinking conversion changes ownership rules
Calling .as_str() does not create independent storage. It only borrows.
let s = String::from("hello");
let slice = s.as_str();
If s goes away, slice cannot outlive it.
Comparisons
| Type | Owns data? | Growable? | Lifetime tied to owner? | Typical use |
|---|---|---|---|---|
String | Yes | Yes | No borrowed lifetime needed | Dynamic text you create or store |
&str | No | No | Yes | Temporary borrowed string view |
&'static str | No | No | Lives for whole program | String literals and fixed messages |
Box<str> | Yes | No | No borrowed lifetime needed | Owned immutable string data |
String vs &str
- Use
Stringwhen you need ownership. - Use
&strwhen you only need to read text temporarily.
&str vs &'static str
&strcan have any valid lifetime.&'static stris only for data valid for the entire program.
String vs leaked &'static str
| Choice | Pros | Cons |
|---|---|---|
String | Safe, owned, flexible | Not static |
Box::leak(... ) -> &'static str | Produces &'static str | Memory is never freed |
&'static str vs String in APIs
| API Type | Best for |
|---|---|
fn f(msg: &str) | Most general read-only string input |
fn f() -> String | Dynamically created output |
fn f() -> &'static str | Fixed compile-time output |
Cheat Sheet
Quick rules
String->&str: yesString->&'static str: not normally- String literal ->
&'static str: yes as_str()borrows; it does not extend lifetime
Common syntax
let s = String::from("hello");
let a: &str = &s;
let b: &str = s.as_str();
Static string literal
let msg: &'static str = "hello";
Return dynamic text
fn make_text() -> String {
String::from("hello")
}
Return fixed text
fn label() -> &'static str {
"ready"
}
Leak to force 'static
fn leak_string(s: String) -> &'static str {
Box::leak(s.into_boxed_str())
}
Use this only when you intentionally want memory to live until program exit.
Good API defaults
- input parameter:
&str - computed return value:
String - fixed constant return value:
&'static str
Key idea
A reference cannot outlive the data it points to.
FAQ
Can I convert a String to &str in Rust?
Yes. Use &s or s.as_str().
Can I convert a String to &'static str safely?
Not in the normal sense. A runtime String does not live for the entire program, so it cannot naturally become &'static str.
Why do string literals have 'static lifetime?
Because they are stored in the program's compiled binary and exist for the whole runtime of the program.
What does as_str() do?
It returns a borrowed &str view into the String. It does not allocate and does not make the data static.
When should I return String instead of &str?
Return String when the text is created dynamically inside the function.
Is Box::leak a valid way to get &'static str?
Yes, but it leaks memory permanently. Use it only when that is an intentional design decision.
Should function parameters use &'static str?
Usually no. Prefer &str unless the function truly requires only program-long static text.
Mini Project
Description
Build a small Rust program that formats a user-facing message and demonstrates the difference between borrowed string slices, owned strings, and static string literals. This project is useful because these three forms appear constantly in real Rust programs such as CLI tools, web handlers, and validators.
Goal
Create a program that accepts dynamic text, prints it through functions using &str, returns computed text as String, and returns fixed labels as &'static str.
Requirements
- Write one function that accepts a
&strand prints it. - Write one function that returns a dynamic
Stringusingformat!. - Write one function that returns a fixed
&'static strlabel. - In
main, create aStringand pass it as a&str. - Print the results of all three functions to show the differences clearly.
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.