Question
I often end up with an Option<String> and want to use either the contained value or a hardcoded default string.
This is straightforward with integers:
let opt: Option<i32> = Some(3);
let value = opt.unwrap_or(0);
With String and &str, however, I get a type mismatch:
let opt: Option<String> = Some("some value".to_owned());
let value = opt.unwrap_or("default string");
The compiler reports:
error[E0308]: mismatched types
--> src/main.rs:4:31
|
4 | let value = opt.unwrap_or("default string");
| ^^^^^^^^^^^^^^^^
| |
| expected struct `std::string::String`, found reference
| help: try using a conversion method: `"default string".to_string()`
|
= note: expected type `std::string::String`
found type `&'static str`
One possible fix is to convert the default into an owned String:
let value = opt.unwrap_or("default string".to_string());
However, that allocates a new String, which I would like to avoid when I only need a string slice immediately afterward, for example:
let rx: Regex = Regex::new(&opt.unwrap_or("default string".to_string()));
What is the idiomatic Rust way to convert Option<String> into Option<&str> so I can use unwrap_or with a string literal and avoid unnecessary allocation?
Short Answer
By the end of this page, you will understand why Option<String> and Option<&str> are different types in Rust, how borrowing works with Option, and the idiomatic ways to turn an owned optional string into a borrowed optional string using as_deref or as_ref().map(...). You will also see how to use unwrap_or with string literals without creating an unnecessary String.
Concept
In Rust, String and &str are related, but they are not the same type:
Stringis an owned, growable string stored on the heap.&stris a borrowed string slice, a view into string data owned elsewhere.
This matters inside Option too:
Option<String>means theOptionmay own aStringOption<&str>means theOptionmay hold a borrowed string slice
Rust does not automatically convert Option<String> into Option<&str> because that would change ownership into borrowing, and borrowing must follow explicit lifetime rules.
When you write this:
opt.unwrap_or("default string")
Rust expects the fallback value to have the same type as the Option content. Since opt is , expects a , not a .
Mental Model
Think of String as owning a book, and &str as pointing to a page in that book.
String= you own the whole book&str= you are just reading from a page
Now wrap that in Option:
Option<String>= maybe you own a bookOption<&str>= maybe you have permission to read a page
If you only need to read, it is wasteful to create a new book just to read a sentence from it. Instead, borrow the page from the book you already have.
That is what as_deref() does: it says, “If there is a String here, let me borrow it as a &str.”
Syntax and Examples
The idiomatic solution is:
let opt: Option<String> = Some("some value".to_string());
let value: &str = opt.as_deref().unwrap_or("default string");
Core syntax
option_string.as_deref()
This converts:
Option<String>
into:
Option<&str>
Example 1: Use the inner string or a default
let opt: Option<String> = Some("hello".to_string());
let value = opt.().();
(, value);
Step by Step Execution
Consider this code:
let opt: Option<String> = Some("some value".to_string());
let value = opt.as_deref().unwrap_or("default string");
Here is what happens step by step:
-
optis created as anOption<String>.- It contains
Some(String::from("some value"))
- It contains
-
opt.as_deref()is called.- Rust borrows the inner
String - It converts
Option<String>intoOption<&str> - Result:
Some("some value")
- Rust borrows the inner
-
.unwrap_or("default string")is called onOption<&str>.
Real World Use Cases
This pattern shows up often in Rust programs that mix owned values and borrowed API inputs.
Common situations
-
Configuration values
- A config loader may return
Option<String> - Your code wants to use a default
&strif missing
- A config loader may return
-
Environment variables
- You may read an env var into
Option<String> - Then pass it to a parser, URL builder, or regex function expecting
&str
- You may read an env var into
-
Command-line arguments
- A user may optionally provide a pattern or name
- If not provided, your program falls back to a hardcoded string
-
Database fields
- Optional text fields often become
Option<String> - Business logic may only need a borrowed string temporarily
- Optional text fields often become
-
Web handlers and APIs
- Incoming optional parameters may be owned strings
- Validation and parsing functions frequently accept
&str
Example: environment variable fallback
std::env;
= env::().();
= name.().();
(, app_name);
Real Codebase Usage
In real Rust codebases, developers usually prefer borrowing when possible and only allocating when ownership is actually needed.
Common pattern: borrow before fallback
let pattern = opt.as_deref().unwrap_or(".*");
This is concise and idiomatic.
Validation before use
A borrowed &str is often passed into validation logic:
fn is_valid_name(name: &str) -> bool {
!name.trim().is_empty()
}
let name = opt.as_deref().unwrap_or("guest");
if is_valid_name(name) {
println!("Valid: {}", name);
}
Guard clause style
Developers often combine optional borrowing with early checks:
fn process_name(opt: <>) {
= opt.().();
name.() {
;
}
(, name);
}
Common Mistakes
1. Passing &str to unwrap_or on Option<String>
Broken code:
let opt: Option<String> = Some("hello".to_string());
let value = opt.unwrap_or("default");
Why it fails:
unwrap_orrequires the same inner type as theOptionOption<String>needs aStringfallback"default"is&str
Fix:
let value = opt.as_deref().unwrap_or("default");
2. Allocating unnecessarily with
Comparisons
| Approach | Result Type | Allocates? | Consumes Option<String>? | Idiomatic for borrowed use? |
|---|---|---|---|---|
opt.unwrap_or("default".to_string()) | String | Yes, for default | Yes | No |
opt.as_deref().unwrap_or("default") | &str | No | No | Yes |
| `opt.as_ref().map( | s | s.as_str()).unwrap_or("default")` | &str | No |
opt.unwrap() |
Cheat Sheet
// Best idiomatic conversion
let value: &str = opt.as_deref().unwrap_or("default");
Type conversions
Option<String> // owned optional string
Option<&String> // borrowed optional String
Option<&str> // borrowed optional string slice
Useful methods
opt.as_ref() // Option<String> -> Option<&String>
opt.as_deref() // Option<String> -> Option<&str>
opt.map(...) // transform inner value
opt.unwrap_or(default) // use default if None
opt.unwrap_or_else(...) // lazy default
Common patterns
let s = opt.().();
FAQ
Why does unwrap_or not accept a &str for Option<String>?
Because unwrap_or requires a fallback of the same inner type as the Option. For Option<String>, that type is String.
What is the most idiomatic way to convert Option<String> to Option<&str>?
Use:
opt.as_deref()
This is the clearest modern Rust solution.
Does as_deref() allocate memory?
No. It only borrows the inner value and converts it to a string slice.
Should I use to_string() for the default value?
Only if you actually need an owned String. If you only need &str, prefer as_deref().unwrap_or(...).
What is the difference between as_ref() and ?
Mini Project
Description
Build a small Rust utility that chooses a username from optional input. The program should accept an Option<String>, borrow it as &str, and fall back to a default username when no value is present. This demonstrates how to work with Option<String> efficiently without allocating a new String just to use a default string literal.
Goal
Create a function that turns optional owned text into a borrowed &str with a default fallback, then use it in a realistic function call.
Requirements
- Write a function that takes
Option<String>as input. - Return or use a
&strfallback value when the option isNone. - Do not allocate a new
Stringfor the default case. - Print the chosen username for both
SomeandNoneexamples.
Keep learning
Related questions
Accessing Cargo Package Metadata in Rust
Learn how to read Cargo package metadata like version, name, and authors in Rust using compile-time environment macros.
Associated Types vs Generic Type Parameters in Rust: When to Use Each
Learn when to use associated types vs generic parameters in Rust traits, with clear rules, examples, and practical API design advice.
Can a Struct Extend Another Struct in Rust? Composition vs Inheritance
Learn how Rust handles struct reuse without inheritance, using composition, traits, and wrapper structs with practical examples.