Очень часто я получаю 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>
чтобы избежать этого распределения.
Как это идоматически написать?
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?map
; это решает вашу проблему?Вы можете использовать
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
lvalue. Затем крайний левый*
фактически вызываетDeref
трейт, потому чтоString
реализуетDeref<Target=str>
, давая намstr
lvalue. Наконец,&
принимает адрес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)); }
источник
map(|x| &**x)
вас тоже можноmap(String::as_ref)
.opt.as_ref()
isOption<&String>
, затемopt.as_ref().map(String::as_ref)
передает это&String
вString::as_ref
, который возвращает&str
. Почему не может&String
отOption::as_ref
быть принужден к&str
?String::as_ref
возвращает an&str
, а не a&&String
(см. здесь )&**
, крайний левый*
фактически вызываетDeref
трейт, потому чтоString
реализуетDeref<Target=str>
.Deref::deref
иAsRef::as_ref
оба обеспечивают преобразование ссылки в ссылку, и бывает, что преобразование из&String
в&str
доступно для обоих. Вы можете использоватьmap(String::deref)
вместоmap(String::as_ref)
, это также будет эквивалентно.Лучше всего реализовать это в общем для
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
.источник
opt.as_deref()
это действительно будет файлOption<&str>
.Несмотря на то, что я люблю ответ 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));
источник
Вот один из способов сделать это. Имейте в виду, что вы должны сохранить оригинал
String
, иначе во что будет&str
нарезаться?let opt = Some(String::from("test")); // kept around let unwrapped: &str = match opt.as_ref() { Some(s) => s, // deref coercion None => "default", };
детский манеж
источник