Подтвердить что ты не робот

Преобразование из опции <String> в Option <& str>

Очень часто я получал Option<String> из расчета и хотел бы использовать это значение или значение по умолчанию в жестком коде.

Это было бы тривиально с целым числом:

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

Но с String и &str компилятор жалуется на несовпадающие типы:

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

Точная ошибка здесь:

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'

Один из вариантов - преобразовать фрагмент строки в принадлежащую строку, как предполагает rustc:

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

Но это вызывает выделение, что нежелательно, когда я хочу немедленно преобразовать результат обратно в фрагмент строки, как в этом вызове к Regex::new():

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

Я бы предпочел преобразовать Option<String> в Option<&str>, чтобы избежать этого распределения.

Что за нелепый способ написать это?

4b9b3361

Ответ 1

Вы можете использовать as_ref() и map(), чтобы преобразовать Option<String> в 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");
}

Во-первых, as_ref() неявно берет ссылку на opt, давая &Option<String> (потому что as_ref() принимает &self, т.е. он получает ссылку), и превращает его в Option<&String>. Затем мы используем map, чтобы преобразовать его в Option<&str>. Вот что делает &**x: самый правый * (который оценивается первым) просто разыменовывает &String, давая значение String l. Затем самый левый * фактически вызывает черту Deref, потому что String реализует Deref<Target=str>, давая нам значение str l. Наконец, & получает адрес str lvalue, давая нам &str.

Вы можете немного упростить это, используя map_or для объединения map и unwrap_or в одной операции:

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

Если &**x кажется вам слишком волшебным, вместо этого вы можете написать String::as_str:

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

или String::as_ref (из черты AsRef, которая входит в прелюдию):

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

или String::deref (хотя вам также необходимо импортировать черту Deref):

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

Чтобы все это работало, вам нужно сохранить владельца для Option<String>, пока Option<&str> или развернутый &str должен оставаться доступным. Если это слишком сложно, вы можете использовать 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));
}

Ответ 2

Лучше всего реализовать это в целом для 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)
    }
}

который эффективно обобщает as_ref.

Ответ 3

В стандартной библиотеке есть нестабильная ночная функция Option::as_deref, которая делает это:

#![feature(inner_deref)]

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

Ответ 4

Хотя я люблю ответ Veedrac (я использовал его), если вам это нужно всего лишь в одной точке, и вам нужно что-то выразительное, вы можете использовать цепочку as_ref(), map и String::as_str:

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

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

Ответ 5

Вот один из способов сделать это. Имейте в виду, что вам нужно сохранить исходный String, в противном случае, что бы &str был фрагментом?

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

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

playpen