Bardzo często otrzymałem Option<String>
z obliczeń i chciałbym użyć tej wartości lub domyślnej wartości zakodowanej na stałe.
Byłoby to trywialne w przypadku liczby całkowitej:
let opt: Option<i32> = Some(3);
let value = opt.unwrap_or(0); // 0 being the default
Ale z a String
i a &str
kompilator narzeka na niezgodne typy:
let opt: Option<String> = Some("some value".to_owned());
let value = opt.unwrap_or("default string");
Dokładny błąd tutaj to:
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`
Jedną z opcji jest przekonwertowanie kawałka łańcucha na posiadany ciąg, jak sugeruje rustc:
let value = opt.unwrap_or("default string".to_string());
Ale to powoduje alokację, która jest niepożądana, gdy chcę natychmiast przekonwertować wynik z powrotem na kawałek ciągu, jak w tym wywołaniu Regex::new()
:
let rx: Regex = Regex::new(&opt.unwrap_or("default string".to_string()));
Wolałbym zamienić na Option<String>
an, Option<&str>
aby uniknąć tego przydziału.
Jaki jest idomatyczny sposób, aby to napisać?
map
nie działała. Nie rozumiem, co dzieje się z pierwszymOptions<String>
i kopią, która się do niego odwołuje w przypadkuas_deref
wariantu kodu. Oto mój działający kod przy użyciuas_deref
:let device_id = UsbDeviceIdentifier::VidPidSn { vid: device.vendor_id, pid: device.product_id, sn: device.serial_number.as_deref().unwrap_or("") };
i moja pierwsza próbalet 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
. OK, ale dlaczego toas_deref
działa, ponieważ nadal tworzy odniesienie do oryginałuOption<String>
zwróconego przez.serial_number
, więc nadal wskazuje na dane, które są jego własnościąOption
!? Czy różnicaas_deref
polega na tym, że transformacja jest wykonywana w miejscu, a nie w ciele zamknięcia, gdzie odrzuca źródło wycinka, który zwraca? Jeśli tak, czy istnieje sposób, aby to naprawić? Jak zwrócić kopię str?map
; czy to rozwiązuje twój problem?Możesz użyć
as_ref()
imap()
przekształcićOption<String>
plikOption<&str>
.fn main() { let opt: Option<String> = Some("some value".to_owned()); let value = opt.as_ref().map(|x| &**x).unwrap_or("default string"); }
Po pierwsze,
as_ref()
niejawnie przyjmuje odniesienieopt
, dając&Option<String>
(ponieważas_ref()
bierze&self
, czyli otrzymuje odniesienie) i zamienia je wOption<&String>
. Następnie używamymap
do konwersji go na plikOption<&str>
. Oto, co&**x
robi: prawy*
(który jest oceniany jako pierwszy) po prostu usuwa odwołanie&String
, dającString
lwartość. Następnie lewy*
faktycznie wywołujeDeref
cechę, ponieważString
implementujeDeref<Target=str>
, dając namstr
lwartość. Na koniec&
przyjmuje adresstr
lwartości, dając nam&str
.Możesz to nieco uprościć, używając
map_or
do łączeniamap
iunwrap_or
w jednej operacji:fn main() { let opt: Option<String> = Some("some value".to_owned()); let value = opt.as_ref().map_or("default string", |x| &**x); }
Jeśli
&**x
wydaje Ci się to zbyt magiczne, możeszString::as_str
zamiast tego napisać :fn main() { let opt: Option<String> = Some("some value".to_owned()); let value = opt.as_ref().map_or("default string", String::as_str); }
lub
String::as_ref
(zAsRef
cechy, która jest w preludium ):fn main() { let opt: Option<String> = Some("some value".to_owned()); let value = opt.as_ref().map_or("default string", String::as_ref); }
lub
String::deref
(chociaż musisz też zaimportowaćDeref
cechę):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); }
Aby którykolwiek z nich działał, musisz zachować właściciela
Option<String>
tak długo, jak długo musi pozostać dostępnyOption<&str>
lub rozpakowany&str
. Jeśli to zbyt skomplikowane, możesz użyć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)); }
źródło
map(|x| &**x)
ciebie też możesz zrobićmap(String::as_ref)
.opt.as_ref()
jestOption<&String>
, a następnieopt.as_ref().map(String::as_ref)
przekazuje to&String
doString::as_ref
, co zwraca&str
. Dlaczego nie&String
odOption::as_ref
być zmuszany do&str
?String::as_ref
zwraca an&str
, not a&&String
(patrz tutaj )&**
, lewy*
faktycznie wywołujeDeref
cechę, ponieważString
implementujeDeref<Target=str>
.Deref::deref
iAsRef::as_ref
oba zapewniają konwersje odniesienia do odniesienia, i zdarza się, że konwersja z&String
na&str
jest dostępna w obu. Możesz użyćmap(String::deref)
zamiastmap(String::as_ref)
, byłoby to również równoważne.Lepszym sposobem byłoby zaimplementowanie tego ogólnego w przypadku
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) } }
co skutecznie uogólnia
as_ref
.źródło
opt.as_deref()
to rzeczywiście jestOption<&str>
.Chociaż kocham odpowiedź Veedrac za (użyłem go), jeśli jest to potrzebne w jednym punkcie i chcesz coś, co jest ekspresyjny można użyć
as_ref()
,map
iString::as_str
łańcuch:let opt: Option<String> = Some("some value".to_string()); assert_eq!(Some("some value"), opt.as_ref().map(String::as_str));
źródło
Oto jeden sposób, w jaki możesz to zrobić. Należy pamiętać, że muszą zachować oryginalne
String
wokół, inaczej co miałby&str
być plasterek na?let opt = Some(String::from("test")); // kept around let unwrapped: &str = match opt.as_ref() { Some(s) => s, // deref coercion None => "default", };
kojec dla dziecka
źródło