141

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?

BinaryButterfly
  • 18,137
  • 13
  • 50
  • 91
George Hilliard
  • 15,402
  • 9
  • 58
  • 96
  • *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. – FreelanceConsultant May 27 '23 at 10:12

5 Answers5

154

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
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 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 Manole 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 Manole 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 Manole 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
  • 2
    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. – user4815162342 Jun 09 '20 at 11:31
67

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é
  • 60,274
  • 7
  • 180
  • 155
  • 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<&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`? – 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<&String>` -> `Option<&str>` 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
25

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
  • 58,273
  • 15
  • 112
  • 169
  • 1
    This is a great utility function. I wish it was part of the standard library as a function on Option! – Bill Fraser Jan 24 '16 at 22:08
  • 1
    Thanks! This works for me -- if I include this code, then `opt.as_deref()` is indeed an `Option<&str>`. – rspeer Jul 07 '16 at 22:28
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
  • 388,571
  • 95
  • 1,107
  • 1,366
Michele d'Amico
  • 22,111
  • 8
  • 69
  • 76
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
  • 36,800
  • 16
  • 93
  • 123