简体   繁体   中英

Converting from Option<String> to Option<&str>

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?

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:

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 , ie 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 theDeref 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 useCow .

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));
}

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 .

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));

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM