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 Option<String>, unwrap_or expects a String, not a &str.
If your goal is to borrow the inner string rather than take ownership of it, the idiomatic approach is to first convert:
Option<String> -> Option<&str>
Then you can use:
unwrap_or("default string")
without allocating.
In modern Rust, the cleanest method is:
opt.as_deref()
This turns an Option<String> into an Option<&str> by borrowing the inner String and dereferencing it to str.
Why this matters in real code:
- avoids unnecessary heap allocation
- avoids moving the
Stringwhen you only need a borrowed view - works well with APIs that accept
&str - keeps ownership clear and efficient
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.as_deref().unwrap_or("default");
println!("{}", value); // hello
If opt is None:
let opt: Option<String> = None;
let value = opt.as_deref().unwrap_or("default");
println!("{}", value); // default
Example 2: Pass directly to a function that needs &str
fn print_message(message: &str) {
println!("{}", message);
}
let opt: Option<String> = Some("welcome".to_string());
print_message(opt.as_deref().unwrap_or("default message"));
Older equivalent form
Before as_deref, or if you want to see what it does more explicitly:
let value = opt.as_ref().map(|s| s.as_str()).unwrap_or("default string");
Here:
as_ref()convertsOption<String>toOption<&String>map(|s| s.as_str())converts&Stringto&str
This is more verbose, but useful for understanding.
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>.- Because the option is
Some, Rust returns the inner&str - The default is ignored
- Because the option is
-
valuebecomes:
"some value"
Now trace the None case:
let opt: Option<String> = None;
let value = opt.as_deref().unwrap_or("default string");
Step by step:
optisNoneopt.as_deref()staysNoneunwrap_or("default string")uses the fallbackvaluebecomes:
"default string"
Regex example
use regex::Regex;
let opt: Option<String> = Some("abc".to_string());
let rx = Regex::new(opt.as_deref().unwrap_or("default pattern")).unwrap();
Execution:
- if
optisSome, the innerStringis borrowed as&str - if
optisNone, the string literal is used - no extra
Stringallocation is needed for the fallback path
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
use std::env;
let name = env::var("APP_NAME").ok();
let app_name = name.as_deref().unwrap_or("MyApp");
println!("Starting {}", app_name);
This avoids allocating a new String just to use a default literal.
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: Option<String>) {
let name = opt.as_deref().unwrap_or("guest");
if name.is_empty() {
return;
}
println!("Processing {}", name);
}
Avoiding unnecessary clones
A common code review improvement is replacing cloning or allocating with borrowing:
// Less ideal
let value = opt.clone().unwrap_or("default".to_string());
// Better when only borrowing is needed
let value = opt.as_deref().unwrap_or("default");
Working with APIs that accept &str
Many Rust APIs prefer &str, including:
- parsers
- regex constructors
- logging helpers
- path or URL processing helpers
- serialization helpers
Borrowing fits these APIs naturally and keeps the code efficient.
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 to_string()
Works, but may be wasteful:
let value = opt.unwrap_or("default".to_string());
This is fine if you truly need an owned String, but not if you only need &str temporarily.
Better:
let value = opt.as_deref().unwrap_or("default");
3. Moving the String when only a borrow is needed
let opt: Option<String> = Some("hello".to_string());
let owned = opt.unwrap();
println!("{}", owned);
This consumes the Option. If you only need to read the contents, borrow instead.
Better:
let borrowed = opt.as_deref().unwrap_or("default");
println!("{}", borrowed);
4. Using as_ref() but forgetting as_str()
as_ref() alone gives Option<&String>, not Option<&str>:
let value = opt.as_ref().unwrap();
That may still work in some places due to deref coercion, but it is not the clearest form when your target is &str.
Clearer:
let value = opt.as_deref().unwrap_or("default");
5. Borrowing from a value that does not live long enough
You cannot return a &str that borrows from a local String that is about to be dropped.
Broken idea:
fn bad() -> &str {
let s = String::from("hello");
s.as_str()
}
Why it fails:
sis dropped at the end of the function- the returned
&strwould point to invalid memory
Rule to remember:
- borrowing is only valid while the owned value still exists
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() | String | No extra allocation | Yes | No, unless ownership is needed |
opt.as_ref() | Option<&String> | No | No | Sometimes, but less direct |
as_deref() vs as_ref()
as_ref()gives a reference to the inner value:
Option<String> -> Option<&String>
as_deref()goes one step further and dereferences to the target type:
Option<String> -> Option<&str>
If your final goal is &str, as_deref() is usually the better choice.
unwrap_or vs unwrap_or_else
For &str defaults, unwrap_or is usually enough:
let value = opt.as_deref().unwrap_or("default");
Use unwrap_or_else when computing the fallback is expensive:
let value = opt.as_deref().unwrap_or_else(|| "default");
For string literals, both are fine, but unwrap_or is simpler.
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.as_deref().unwrap_or("default");
let s = opt.as_ref().map(|x| x.as_str()).unwrap_or("default");
When to use what
- Need ownership? Use
String - Need read-only temporary access? Use
&str - Have
Option<String>but want&str? Useas_deref()
Important rule
unwrap_or must receive the same inner type as the Option it is called on.
Option<String>needsStringOption<&str>needs&str
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 as_deref()?
as_ref()givesOption<&String>as_deref()givesOption<&str>
Can I pass the result directly into functions like Regex::new?
Yes:
let rx = Regex::new(opt.as_deref().unwrap_or("default pattern"))?;
Does as_deref() consume the option?
No. It borrows from the option, so the original Option<String> is not consumed.
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
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.