विकल्प <स्ट्रिंग> से विकल्प <और str> में परिवर्तित करना


85

बहुत बार मैंने Option<String>एक गणना से प्राप्त किया है , और मैं या तो इस मूल्य या एक डिफ़ॉल्ट हार्डकोडेड मूल्य का उपयोग करना चाहूंगा।

यह पूर्णांक के साथ तुच्छ होगा:

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

लेकिन एक Stringऔर एक के साथ &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`

एक विकल्प यह है कि स्ट्रिंग स्लाइस को एक स्वामित्व वाली स्ट्रिंग में परिवर्तित किया जाए, जैसा कि सरसराहट द्वारा सुझाया गया है:

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

रूस्ट 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 की एक प्रति लौटाते हैं?
पॉल-सेबेस्टियन मैनोल

@ पॉल-सेबस्टियन मैनोल कृपया मौजूदा उत्तरmap पढ़ें जो दिखाता है कि कैसे उपयोग करना है ; क्या इससे आपकी समस्या हल होती है?
शमपास्टर

हाँ, लेकिन मैं अभी भी हैरान हूँ।
पॉल-सेबेस्टियन मैनोल

@ पॉल- सेबस्टियन
मैनोल

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इसे a में बदलने के लिए उपयोग करते हैं Option<&str>। यहाँ क्या &**xकरता है: सबसे सही *(जिसका पहले मूल्यांकन किया जाता है) बस &Stringएक संदर्भ देता है , एक Stringअंतराल देता है। फिर, बाईं ओर *वास्तव में Derefविशेषता का आह्वान किया जाता है, क्योंकि String कार्यान्वयन Deref<Target=str> , हमें एक strअंतराल देता है। अंत में, लेवल्यू &का पता लेता है str, जिससे हमें ए &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>या unwrapped &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

2
यह अच्छी तरह से काम करता है, हालांकि अभी भी थोड़ी सी क्रिया है। स्पष्ट करने के लिए, प्रकार opt.as_ref()है Option<&String>, फिर इसे opt.as_ref().map(String::as_ref)पास करता &Stringहै String::as_ref, जो एक रिटर्न देता है &str। क्यों नहीं &Stringसे Option::as_refएक करने के लिए मजबूर किया जा सकता है &str?
जॉर्ज हिलियार्ड

1
@thththreeforty String::as_refरिटर्न &stra, not a &&String( यहाँ देखें )
fjh

@fjh मैं सिर्फ महसूस किया और संपादित :)। मेरा ज़बरदस्त सवाल बना हुआ है।
जॉर्ज हिलियार्ड

1
@ थोड़ा-यार: वास्तव में &**, बाईं ओर *वास्तव में Derefविशेषता को Stringलागू करता है , क्योंकि लागू करता है Deref<Target=str>Deref::derefऔर AsRef::as_refदोनों संदर्भ करने वाली संदर्भ रूपांतरण, और यह होता है कि से परिवर्तित &Stringकरने के लिए &strदोनों के साथ उपलब्ध है। आप map(String::deref)इसके बजाय उपयोग कर सकते हैं map(String::as_ref), यह भी बराबर होगा।
फ्रांसिस गगन

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


1
यह एक शानदार यूटिलिटी फंक्शन है। काश, यह विकल्प <T> पर एक समारोह के रूप में मानक पुस्तकालय का हिस्सा होता!
बिल फ्रेज़र

1
धन्यवाद! यह मेरे लिए काम करता है - अगर मैं इस कोड को शामिल करता हूं, तो opt.as_deref()वास्तव में ए Option<&str>
rspeer

7

हालांकि मुझे विद्रेक का जवाब पसंद है (मैंने इसका इस्तेमाल किया), अगर आपको सिर्फ एक बिंदु पर इसकी आवश्यकता है और आप कुछ ऐसा चाहते हैं जो अभिव्यंजक है जिसका आप उपयोग कर सकते हैं 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
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.