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

85

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

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

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

Но с a Stringи a &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`

Один из вариантов - преобразовать строковый фрагмент в принадлежащую String, как предлагает 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>чтобы избежать этого распределения.

Как это идоматически написать?

Джордж Хиллиард
источник

Ответы:

66

Начиная с Rust 1.40, стандартная библиотека Option::as_derefдолжна делать это:

fn main() {
    let opt: Option<String> = Some("some value".to_owned());
    let value = opt.as_deref().unwrap_or("default string");
}
Шепмастер
источник
У меня это работает, но я не могу понять, почему моя другая версия mapне сработала. Я не понимаю, что происходит с первой Options<String>и той копией, которая на нее ссылается в случае as_derefварианта кода. Вот мой рабочий код, использующий as_deref: let device_id = UsbDeviceIdentifier::VidPidSn { vid: device.vendor_id, pid: device.product_id, sn: device.serial_number.as_deref().unwrap_or("") };и моя первая попытка let device_id = UsbDeviceIdentifier::VidPidSn { vid: device.vendor_id, pid: device.product_id, sn: device.serial_number.map(|s| s.as_str()).unwrap_or("") };.
Пол-Себастьян Маноле
Моя ошибка error[E0515]: cannot return value referencing function parameter `s`, s.as_str() returns a value referencing data owned by the current function. Хорошо, но почему as_derefработает, потому что он по-прежнему создает ссылку на оригинал, Option<String>возвращаемый .serial_number, так что по-прежнему указывает на данные, принадлежащие этому Option!? Есть ли разница в том as_deref, что преобразование выполняется на месте, а не в теле замыкания, где он отбрасывает источник возвращаемого среза? Если это так, есть ли способ это исправить? Как вернуть копию str?
Пол-Себастьян Маноле
@ Paul-SebastianManole, пожалуйста, прочтите существующий ответ, в котором показано, как использоватьmap ; это решает вашу проблему?
Shepmaster
Да, но я все еще озадачен.
Пол-Себастьян Маноле
@ Paul-SebastianManole, как насчет Результат варианта :: карта не живет достаточно долго
Шепмастер
55

Вы можете использовать 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объект, давая Stringlvalue. Затем крайний левый *фактически вызывает Derefтрейт, потому что String реализует Deref<Target=str> , давая нам strlvalue. Наконец, &принимает адрес strlvalue, давая нам &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));
}
Фрэнсис Ганье
источник
13
bikesheddy комментарий: вместо map(|x| &**x)вас тоже можно map(String::as_ref).
fjh 05
2
Это прекрасно работает, хотя и немного многословно. Чтобы уточнить, тип opt.as_ref()is Option<&String>, затем opt.as_ref().map(String::as_ref)передает это &Stringв String::as_ref, который возвращает &str. Почему не может &Stringот Option::as_refбыть принужден к &str?
Джордж Хиллиард
1
@thirtythreeforty String::as_refвозвращает an &str, а не a &&String(см. здесь )
fjh 05
@fjh Я только что понял это и отредактировал :). Мой вопрос о принуждении остается.
Джордж Хиллиард
1
@ little-dude: In &**, крайний левый *фактически вызывает Derefтрейт, потому что Stringреализует Deref<Target=str>. Deref::derefи AsRef::as_refоба обеспечивают преобразование ссылки в ссылку, и бывает, что преобразование из &Stringв &strдоступно для обоих. Вы можете использовать map(String::deref)вместо map(String::as_ref), это также будет эквивалентно.
Francis
20

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

Veedrac
источник
1
Это отличная полезная функция. Хотелось бы, чтобы она была частью стандартной библиотеки как функция Option <T>!
Билл Фрейзер,
1
Благодаря! У меня это работает - если я включу этот код, то opt.as_deref()это действительно будет файл Option<&str>.
rspeer 07
7

Несмотря на то, что я люблю ответ 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));
Микеле д'Амико
источник
1

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

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

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

детский манеж

Хорхе Исраэль Пенья
источник
2
Это не превратило его в вариант <& str>.
rspeer 07