Konvertieren von Option <String> in Option <& str>

80

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 Stringund a &strbeschwert 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?

George Hilliard
quelle

Antworten:

58

Ab Rust 1.40 muss die Standardbibliothek Folgendes Option::as_dereftun:

fn main() {
    let opt: Option<String> = Some("some value".to_owned());
    let value = opt.as_deref().unwrap_or("default string");
}
Shepmaster
quelle
Es funktioniert für mich, aber ich kann nicht verstehen, warum meine andere Version mapnicht funktioniert hat. Ich verstehe nicht, was mit der ersten Options<String>und der Kopie passiert, die bei der as_derefVariante des Codes darauf verweist . Hier ist mein Arbeitscode mit as_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 Versuch let device_id = UsbDeviceIdentifier::VidPidSn { vid: device.vendor_id, pid: device.product_id, sn: device.serial_number.map(|s| s.as_str()).unwrap_or("") };.
Paul-Sebastian Manole
Mein Fehler ist 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 das as_deref, weil immer noch ein Verweis auf das von zurückgegebene Original erstellt Option<String>wird .serial_number, sodass immer noch auf Daten verwiesen wird, die dem gehören Option!? Ist der Unterschied, as_derefder 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?
Paul-Sebastian Manole
@ Paul-SebastianManole Bitte lesen Sie die vorhandene Antwort, die zeigt, wie man verwendetmap ; löst das dein Problem?
Shepmaster
Ja, aber ich bin immer noch verwirrt.
Paul-Sebastian Manole
@ Paul-SebastianManole wie wäre es mit Ergebnis von Option :: map lebt nicht lange genug
Shepmaster
54

Sie können ein verwenden as_ref()und map()ein Option<String>in ein verwandeln 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");
}

Nehmen Sie zunächst as_ref()implizit eine Referenz auf opt, geben Sie eine &Option<String>(weil as_ref()nimmt &self, dh es erhält eine Referenz), und verwandeln Sie sie in eine Option<&String>. Dann mapkonvertieren wir es in ein Option<&str>. Hier ist was&**x tut: die rechte *(die zuerst ausgewertet wird) einfach dereferenziert die &Stringeinen geben Stringlvalue. Dann *ruft das am weitesten links stehende DerefMerkmal tatsächlich auf , weil es String implementiert wird Deref<Target=str> , was uns einen strWert gibt. Schließlich &nimmt das die Adresse des strWertes und gibt uns eine &str.

Sie können dies etwas weiter vereinfachen, indem Sie map_orkombinieren mapund unwrap_orin 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 &**xIhnen das zu magisch erscheint, können Sie String::as_strstattdessen 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 dem AsRefMerkmal, 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 das DerefMerkmal 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 das Option<&str>oder das Unverpackte &strverfü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));
}
Francis Gagné
quelle
13
bikesheddy Kommentar: stattdessen map(|x| &**x)könntest du auch machen map(String::as_ref).
fjh
2
Das funktioniert gut, wenn auch noch etwas ausführlich. Um zu klären, welche Art von opt.as_ref()heißt Option<&String>, dann opt.as_ref().map(String::as_ref)geht dies &Stringzu String::as_ref, was eine zurückgibt &str. Warum kann das &Stringvon Option::as_refnicht zu einem gezwungen werden &str?
George Hilliard
1
@ dreißigdreiforty String::as_refgibt ein &str, nicht ein &&String(siehe hier )
fjh
@fjh Ich habe das gerade erkannt und bearbeitet :). Meine Zwangsfrage bleibt.
George Hilliard
1
@ little-dude: In ruft der ganz &**linke *tatsächlich das DerefMerkmal auf, weil Stringimplementiert Deref<Target=str>. Deref::derefund AsRef::as_refbeide bieten Referenz-zu-Referenz-Konvertierungen, und es kommt vor, dass die Konvertierung von &Stringnach &strmit beiden verfügbar ist. Sie könnten map(String::deref)stattdessen verwenden map(String::as_ref), es wäre auch gleichwertig.
Francis Gagné
20

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.

Veedrac
quelle
1
Dies ist eine großartige Utility-Funktion. Ich wünschte, es wäre Teil der Standardbibliothek als Funktion von Option <T>!
Bill Fraser
1
Vielen Dank! Das funktioniert bei mir - wenn ich diesen Code einbinde, dann opt.as_deref()ist das in der Tat ein Option<&str>.
rspeer
7

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(), mapund String::as_strverketten:

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

assert_eq!(Some("some value"), opt.as_ref().map(String::as_str));
Michele d'Amico
quelle
2

Hier ist eine Möglichkeit, wie Sie es tun können. Beachten Sie, dass Sie haben das Original zu halten Stringum, sonst was wäre das &strein 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

Jorge Israel Peña
quelle
1
Das hat es nicht zu einer Option <& str> gemacht.
rspeer