Sehr oft habe ich eine Option<String>
aus einer Berechnung erhalten und möchte entweder diesen Wert oder einen fest codierten Standardwert verwenden.
Dies wäre mit einer ganzen Zahl trivial:
let opt: Option<i32> = Some(3);
let value = opt.unwrap_or(0); // 0 being the default
Aber mit a String
und a &str
beschwert sich der Compiler über nicht übereinstimmende Typen:
let opt: Option<String> = Some("some value".to_owned());
let value = opt.unwrap_or("default string");
Der genaue Fehler hier ist:
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`
Eine Möglichkeit besteht darin, das String-Slice in einen eigenen String zu konvertieren, wie von rustc vorgeschlagen:
let value = opt.unwrap_or("default string".to_string());
Dies führt jedoch zu einer Zuordnung, die unerwünscht ist, wenn ich das Ergebnis sofort wieder in einen String-Slice konvertieren möchte, wie in diesem Aufruf an Regex::new()
:
let rx: Regex = Regex::new(&opt.unwrap_or("default string".to_string()));
Ich würde das lieber konvertieren Option<String>
, um ein Option<&str>
diese Zuordnung zu vermeiden.
Was ist die idomatische Art, dies zu schreiben?
map
nicht funktioniert hat. Ich verstehe nicht, was mit der erstenOptions<String>
und der Kopie passiert, die bei deras_deref
Variante des Codes darauf verweist . Hier ist mein Arbeitscode mitas_deref
:let device_id = UsbDeviceIdentifier::VidPidSn { vid: device.vendor_id, pid: device.product_id, sn: device.serial_number.as_deref().unwrap_or("") };
und mein erster Versuchlet 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, aber warum funktioniert dasas_deref
, weil immer noch ein Verweis auf das von zurückgegebene Original erstelltOption<String>
wird.serial_number
, sodass immer noch auf Daten verwiesen wird, die dem gehörenOption
!? Ist der Unterschied,as_deref
der eine Transformation an Ort und Stelle bewirkt, im Gegensatz dazu, dass sie im Körper des Verschlusses durchgeführt wird, wo die Quelle des zurückgegebenen Slice verworfen wird? Wenn dem so ist, gibt es eine Möglichkeit, das zu beheben? Wie eine Kopie der str zurückgeben?map
; löst das dein Problem?Sie können ein verwenden
as_ref()
undmap()
einOption<String>
in ein verwandelnOption<&str>
.fn main() { let opt: Option<String> = Some("some value".to_owned()); let value = opt.as_ref().map(|x| &**x).unwrap_or("default string"); }
Nehmen Sie zunächst
as_ref()
implizit eine Referenz aufopt
, geben Sie eine&Option<String>
(weilas_ref()
nimmt&self
, dh es erhält eine Referenz), und verwandeln Sie sie in eineOption<&String>
. Dannmap
konvertieren wir es in einOption<&str>
. Hier ist was&**x
tut: die rechte*
(die zuerst ausgewertet wird) einfach dereferenziert die&String
einen gebenString
lvalue. Dann*
ruft das am weitesten links stehendeDeref
Merkmal tatsächlich auf , weil esString
implementiert wirdDeref<Target=str>
, was uns einenstr
Wert gibt. Schließlich&
nimmt das die Adresse desstr
Wertes und gibt uns eine&str
.Sie können dies etwas weiter vereinfachen, indem Sie
map_or
kombinierenmap
undunwrap_or
in einer einzigen Operation verwenden:fn main() { let opt: Option<String> = Some("some value".to_owned()); let value = opt.as_ref().map_or("default string", |x| &**x); }
Wenn
&**x
Ihnen das zu magisch erscheint, können SieString::as_str
stattdessen schreiben :fn main() { let opt: Option<String> = Some("some value".to_owned()); let value = opt.as_ref().map_or("default string", String::as_str); }
oder
String::as_ref
(aus demAsRef
Merkmal, das im Vorspiel steht ):fn main() { let opt: Option<String> = Some("some value".to_owned()); let value = opt.as_ref().map_or("default string", String::as_ref); }
oder
String::deref
(obwohl Sie dasDeref
Merkmal auch importieren müssen ):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); }
Damit beides funktioniert, müssen Sie einen Eigentümer
Option<String>
so lange behalten, wie dasOption<&str>
oder das Unverpackte&str
verfügbar bleiben muss. Wenn das zu kompliziert ist, könnten Sie verwendenCow
.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)); }
quelle
map(|x| &**x)
könntest du auch machenmap(String::as_ref)
.opt.as_ref()
heißtOption<&String>
, dannopt.as_ref().map(String::as_ref)
geht dies&String
zuString::as_ref
, was eine zurückgibt&str
. Warum kann das&String
vonOption::as_ref
nicht zu einem gezwungen werden&str
?String::as_ref
gibt ein&str
, nicht ein&&String
(siehe hier )&**
linke*
tatsächlich dasDeref
Merkmal auf, weilString
implementiertDeref<Target=str>
.Deref::deref
undAsRef::as_ref
beide bieten Referenz-zu-Referenz-Konvertierungen, und es kommt vor, dass die Konvertierung von&String
nach&str
mit beiden verfügbar ist. Sie könntenmap(String::deref)
stattdessen verwendenmap(String::as_ref)
, es wäre auch gleichwertig.Ein schöner Weg könnte sein, dies generisch zu implementieren für
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) } }
was effektiv verallgemeinert
as_ref
.quelle
opt.as_deref()
ist das in der Tat einOption<&str>
.Obwohl ich die Antwort von Veedrac liebe (ich habe sie verwendet), können Sie sie verwenden, wenn Sie sie nur an einer Stelle benötigen und etwas Ausdrucksstarkes möchten
as_ref()
,map
undString::as_str
verketten:let opt: Option<String> = Some("some value".to_string()); assert_eq!(Some("some value"), opt.as_ref().map(String::as_str));
quelle
Hier ist eine Möglichkeit, wie Sie es tun können. Beachten Sie, dass Sie haben das Original zu halten
String
um, sonst was wäre das&str
ein Stück sein , in?let opt = Some(String::from("test")); // kept around let unwrapped: &str = match opt.as_ref() { Some(s) => s, // deref coercion None => "default", };
Laufstall
quelle