116

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 idomatic way to write this?

Shepmaster
  • 326,504
  • 69
  • 892
  • 1,159
George Hilliard
  • 14,464
  • 7
  • 56
  • 92

5 Answers5

127

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:

Shepmaster
  • 326,504
  • 69
  • 892
  • 1,159
  • 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` 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("") };`. – Paul-Sebastian Feb 17 '20 at 12:44
  • 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` 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-Sebastian Feb 17 '20 at 12:47
  • @Paul-SebastianManole please read [the existing answer that shows how to use `map`](https://stackoverflow.com/a/31234028/155423); does that solve your issue? – Shepmaster Feb 17 '20 at 16:22
  • Yeah, but I'm still puzzled. – Paul-Sebastian Feb 17 '20 at 22:13
  • @Paul-SebastianManole how about [Result of Option::map does not live long enough](https://stackoverflow.com/q/35249309/155423) – Shepmaster Feb 18 '20 at 00:06
  • 1
    Interestingly, I can't get `as_deref()` to convert `Option` to `Option`. (The `Option` 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` is not `Copy`, and `.cloned().as_deref()` works, but at the cost of a cloning. – user4815162342 Jun 09 '20 at 11:31
64

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));
}
Francis Gagné
  • 52,934
  • 3
  • 149
  • 138
  • 13
    bikesheddy comment: instead of `map(|x| &**x)` you could also do `map(String::as_ref)`. – fjh Jul 05 '15 at 19:28
  • 2
    This works nicely, although still a little verbose. To clarify, the type of `opt.as_ref()` is `Option`, 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`? – George Hilliard Jul 05 '15 at 20:20
  • 1
    @thirtythreeforty `String::as_ref` returns an `&str`, not a `&&String` (see [here](http://doc.rust-lang.org/collections/string/struct.String.html#method.as_ref)) – fjh Jul 05 '15 at 20:25
  • @fjh I just realized that and edited :). My coercion question remains. – George Hilliard Jul 05 '15 at 20:25
  • @thirtythreeforty Your question is why deref coercions don't work for `Option` -> `Option` even though they work for `&String` -> `&str`? If so, I'm out of my depth, sorry. – fjh Jul 05 '15 at 20:31
  • Yes, that's my question. I suppose it's a little more obvious that that doesn't work once you spell it out: there's no relationship between `Option` and `Option`, in general. – George Hilliard Jul 05 '15 at 20:32
  • The `&**x` doesn't work for me in Rust 1.9. It says "error: type `str` cannot be dereferenced". – rspeer Jul 07 '16 at 22:25
  • Can someone explain the `&**` part? How is `&**` equivalent to `as_ref` ? – little-dude Mar 01 '17 at 05:44
  • 1
    @little-dude: In `&**`, the leftmost `*` actually invokes the `Deref` trait, because `String` implements `Deref`. `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. – Francis Gagné Mar 01 '17 at 23:46
23

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.

Veedrac
  • 54,508
  • 14
  • 106
  • 164
10

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));
Shepmaster
  • 326,504
  • 69
  • 892
  • 1,159
Michele d'Amico
  • 19,631
  • 8
  • 62
  • 71
1

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

Jorge Israel Peña
  • 34,719
  • 16
  • 88
  • 119