159

Very often I have obtained an Option<String> from a calculation, and I would like to either use this value or a default hardcoded value.

This would be trivial with an integer:

let opt: Option<i32> = Some(3);
let value = opt.unwrap_or(0); // 0 being the default

But with a String and a &str, the compiler complains about mismatched types:

let opt: Option<String> = Some("some value".to_owned());
let value = opt.unwrap_or("default string");

The exact error here is:

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 option is to convert the string slice into an owned String, as suggested by rustc:

let value = opt.unwrap_or("default string".to_string());

But this causes an allocation, which is undesirable when I want to immediately convert the result back to a string slice, as in this call to Regex::new():

let rx: Regex = Regex::new(&opt.unwrap_or("default string".to_string()));

I would rather convert the Option<String> to an Option<&str> to avoid this allocation.

What is the idiomatic way to write this?

7
  • 1
    which is undesirable -> Is it really undesirable? Are you severely constrained by memory? Do you need to push every CPU cycle to the absolute limit? If either of these are true you are using the wrong language. "default string".to_string()) is probably the correct solution here, because it keeps your code easy to understand, which is more important than saving 100 ns of CPU time, or 100 bytes of memory. Commented May 27, 2023 at 10:12
  • @user2138149 How is Rust "the wrong language" for these purposes? It's literally designed to be a safer alternative to C. It performs about equally as well as C in terms of both performance and memory use. Commented Mar 15 at 17:05
  • @Kanashimi I asked the above question because typically when someone says "I want to do X to avoid a few nanoseconds of runtime" they are usually focusing on the wrong thing. Hence why I asked Are you severely constrained by memory? Do you need to push every CPU cycle to the absolute limit? Commented Mar 16 at 14:21
  • @user2138149 I know that, but that does not explain the part where you said "if either of those are true you are using the wrong language", which is specifically what I was referring to. Commented Mar 16 at 15:33
  • @Kanashimi If Rust doesn't provide the performance OP is looking for, and the performance is genuinely important, this suggests there is another language which OP knows of which does provide the required performance. Therefore the conclusion is Rust does not provide the performance which satisfies the requirements by OP and is therefore the wrong tool for the job. Commented Mar 17 at 9:37

5 Answers 5

194

As of Rust 1.40, the standard library has Option::as_deref to do this:

fn main() {
    let opt: Option<String> = Some("some value".to_owned());
    let value = opt.as_deref().unwrap_or("default string");
}

See also:

Sign up to request clarification or add additional context in comments.

7 Comments

It works for me but I can't understand why my other version using map didn't work. I don't understand what happens to the first Options<String> and the copy that references it in the case of the as_deref variant of the code. Here's my working code using as_deref: let device_id = UsbDeviceIdentifier::VidPidSn { vid: device.vendor_id, pid: device.product_id, sn: device.serial_number.as_deref().unwrap_or("") }; and my first try let device_id = UsbDeviceIdentifier::VidPidSn { vid: device.vendor_id, pid: device.product_id, sn: device.serial_number.map(|s| s.as_str()).unwrap_or("") };.
My error is error[E0515]: cannot return value referencing function parameter `s`, s.as_str() returns a value referencing data owned by the current function. OK, but why does as_deref work, because it still creates a reference to the original Option<String> returned by .serial_number, so that still points to data owned by that Option!? Is the difference that as_deref does a transformation in place as opposed to doing it in the closure's body, where it is discarding the source of the slice it returns? If that's so, is there a way to fix that? Like return a copy of the str?
@Paul-SebastianManole please read the existing answer that shows how to use map; does that solve your issue?
Yeah, but I'm still puzzled.
Interestingly, I can't get as_deref() to convert Option<&String> to Option<&str>. (The Option<&String> is obtained by a hash table lookup.) Of course, .map(String::as_str) works, but I wonder if there is a "standard" combinator to achieve the same. For example, .copied().as_deref() doesn't work either because Option<&String> is not Copy, and .cloned().as_deref() works, but at the cost of a cloning.
|
70

You can use as_ref() and map() to transform an Option<String> into an Option<&str>.

fn main() {
    let opt: Option<String> = Some("some value".to_owned());
    let value = opt.as_ref().map(|x| &**x).unwrap_or("default string");
}

First, as_ref() implicitly takes a reference on opt, giving an &Option<String> (because as_ref() takes &self, i.e. it receives a reference), and turns it into an Option<&String>. Then we use map to convert it to an Option<&str>. Here's what &**x does: the rightmost * (which is evaluated first) simply dereferences the &String, giving a String lvalue. Then, the leftmost * actually invokes the Deref trait, because String implements Deref<Target=str>, giving us a str lvalue. Finally, the & takes the address of the str lvalue, giving us a &str.

You can simplify this a bit further by using map_or to combine map and unwrap_or in a single operation:

fn main() {
    let opt: Option<String> = Some("some value".to_owned());
    let value = opt.as_ref().map_or("default string", |x| &**x);
}

If &**x looks too magical to you, you can write String::as_str instead:

fn main() {
    let opt: Option<String> = Some("some value".to_owned());
    let value = opt.as_ref().map_or("default string", String::as_str);
}

or String::as_ref (from the AsRef trait, which is in the prelude):

fn main() {
    let opt: Option<String> = Some("some value".to_owned());
    let value = opt.as_ref().map_or("default string", String::as_ref);
}

or String::deref (though you need to import the Deref trait too):

use std::ops::Deref;

fn main() {
    let opt: Option<String> = Some("some value".to_owned());
    let value = opt.as_ref().map_or("default string", String::deref);
}

For either of these to work, you need to keep an owner for the Option<String> as long as the Option<&str> or unwrapped &str needs to remain available. If that's too complicated, you could use Cow.

use std::borrow::Cow::{Borrowed, Owned};

fn main() {
    let opt: Option<String> = Some("some value".to_owned());
    let value = opt.map_or(Borrowed("default string"), |x| Owned(x));
}

10 Comments

bikesheddy comment: instead of map(|x| &**x) you could also do map(String::as_ref).
This works nicely, although still a little verbose. To clarify, the type of opt.as_ref() is Option<&String>, then opt.as_ref().map(String::as_ref) passes this &String to String::as_ref, which returns an &str. Why can't the &String from Option::as_ref be coerced to an &str?
@thirtythreeforty String::as_ref returns an &str, not a &&String (see here)
@fjh I just realized that and edited :). My coercion question remains.
@little-dude: In &**, the leftmost * actually invokes the Deref trait, because String implements Deref<Target=str>. Deref::deref and AsRef::as_ref both provide reference-to-reference conversions, and it happens that converting from &String to &str is available with both. You could use map(String::deref) instead of map(String::as_ref), it would also be equivalent.
|
27

A nicer way could be to implement this generically for T: Deref:

use std::ops::Deref;

trait OptionDeref<T: Deref> {
    fn as_deref(&self) -> Option<&T::Target>;
}

impl<T: Deref> OptionDeref<T> for Option<T> {
    fn as_deref(&self) -> Option<&T::Target> {
        self.as_ref().map(Deref::deref)
    }
}

which effectively generalizes as_ref.

2 Comments

This is a great utility function. I wish it was part of the standard library as a function on Option<T>!
Thanks! This works for me -- if I include this code, then opt.as_deref() is indeed an Option<&str>.
11

Although I love Veedrac's answer (I used it), if you need it at just one point and you would like something that is expressive you can use as_ref(), map and String::as_str chain:

let opt: Option<String> = Some("some value".to_string());

assert_eq!(Some("some value"), opt.as_ref().map(String::as_str));

Comments

3

Here's one way you can do it. Keep in mind that you have to keep the original String around, otherwise what would the &str be a slice into?

let opt = Some(String::from("test")); // kept around

let unwrapped: &str = match opt.as_ref() {
  Some(s) => s, // deref coercion
  None => "default",
};

playpen

1 Comment

That didn't turn it into an Option<&str>.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.