कॉपी प्रकारों के लिए उधार राशि का विरोध?

Aug 18 2020

मैंने उन Borrowविशेषताओं को परिभाषित करने के लिए उपयोग किया है जो एक स्वामित्व प्रकार या एक संदर्भ, जैसे Tया दोनों को स्वीकार करने वाले कार्यों को परिभाषित करते हैं &Tborrow()विधि तो समारोह प्राप्त करने के लिए कहा जाता है &T

क्या कुछ लक्षण हैं जो विपरीत (यानी एक फ़ंक्शन जो स्वीकार करता है Tया &Tप्राप्त करता है T) को Copyप्रकारों के लिए अनुमति देता है ?

इस उदाहरण के लिए:

use std::borrow::Borrow;

fn foo<T: Borrow<u32>>(value: T) -> u32 {
    *value.borrow()
}

fn main() {
    println!("{}", foo(&5));
    println!("{}", foo(5));
}

यह borrow()एक संदर्भ प्राप्त करने के लिए कॉल करता है, जिसे तब तुरंत हटा दिया जाता है।

क्या एक और कार्यान्वयन है जो सिर्फ मूल्य को कॉपी करता है अगर Tमें पारित किया गया था, और यदि &Tदिया गया था? या इस तरह की बात लिखने का मुहावरेदार तरीका ऊपर है?

जवाब

5 trentcl Aug 18 2020 at 14:09

वास्तव में इसके लिए एक उलटा गुण नहीं है Borrow, क्योंकि यह वास्तव में उपयोगी नहीं है क्योंकि फ़ंक्शन उसी तरह Borrowसे बाध्य है। इसका कारण स्वामित्व से है।

"व्युत्क्रम Borrow" से कम उपयोगी क्यों है Borrow?

ऐसे कार्य जिन्हें संदर्भ की आवश्यकता है

एक फ़ंक्शन पर विचार करें जिसे केवल अपने तर्क को संदर्भित करने की आवश्यकता है:

fn puts(arg: &str) {
    println!("{}", arg);
}

स्वीकार Stringकरना यहां मूर्खतापूर्ण होगा, क्योंकि putsडेटा का स्वामित्व लेने की आवश्यकता नहीं है, लेकिन स्वीकार करने का &strमतलब है कि हम कभी-कभी कॉलर को डेटा को आवश्यक से अधिक समय तक रखने के लिए मजबूर कर सकते हैं:

{
    let output = create_some_string();
    output.push_str(some_other_string);
    puts(&output);
    // do some other stuff but never use `output` again
} // `output` isn't dropped until here

पास होने के outputबाद इस समस्या की आवश्यकता नहीं है puts, और कॉल करने वाले को यह पता है, लेकिन putsएक संदर्भ की आवश्यकता है, इसलिए outputब्लॉक के अंत तक जीवित रहना होगा। स्पष्ट रूप से आप कॉलर को कभी-कभी अधिक ब्लॉक जोड़कर और कभी-कभी ए से इसे ठीक कर सकते हैं let, लेकिन putsकॉल करने वाले को सफाई की जिम्मेदारी सौंपने के लिए सामान्य भी बनाया जा सकता है output:

fn puts<T: Borrow<str>>(arg: T) {
    println!("{}", arg.borrow());
}

स्वीकारना T: Borrowके लिए putsफोन करने वाले का फैसला करने के तर्क के आसपास रखने के लिए किया जाए या यह समारोह में जाने के लिए लचीलापन प्रदान करता है।

ऐसे कार्य जिन्हें स्वामित्व मूल्यों की आवश्यकता है

अब उस फ़ंक्शन के मामले पर विचार करें, जिसे वास्तव में स्वामित्व लेने की आवश्यकता है:

struct Wrapper(String);
fn wrap(arg: String) -> Wrapper {
    Wrapper(arg)
}

इस मामले में स्वीकार &strकरना मूर्खतापूर्ण wrapहोगा , क्योंकि इस पर कॉल to_owned()करना होगा। यदि कॉलर के पास ऐसा Stringउपयोग है जो अब उपयोग नहीं हो रहा है, तो यह अनावश्यक रूप से उस डेटा को कॉपी करेगा जो कि फ़ंक्शन में स्थानांतरित हो सकता है। इस मामले में, स्वीकार Stringकरना अधिक लचीला विकल्प है, क्योंकि यह कॉल करने वाले को यह तय करने की अनुमति देता है कि कोई क्लोन बनाना है या किसी मौजूदा को पारित करना है String। "उलटा Borrow" विशेषता होने से कोई लचीलापन नहीं होगा जो arg: Stringपहले से ही प्रदान नहीं करता है।

लेकिन Stringहमेशा नहीं, सबसे ergonomic तर्क है स्ट्रिंग के कई अलग अलग प्रकार के होते हैं क्योंकि: &str, Cow<str>, Box<str>... हम कर सकते हैं wrapथोड़ा और कहा कि यह कुछ भी है कि परिवर्तित किया जा सकता स्वीकार करता है के द्वारा ergonomic intoएक String

fn wrap<T: Into<String>>(arg: T) -> Wrapper {
    Wrapper(arg.into())
}

इसका मतलब है कि आप इसे कॉल कर सकते हैं जैसे कि शाब्दिक पर wrap("hello, world")कॉल किए बिना .to_owned()। जो वास्तव में एक लचीलापन जीत नहीं है - कॉलर हमेशा .into()सामान्यता के नुकसान के बिना कॉल कर सकता है - लेकिन यह एक एर्गोनोमिक जीत है।

Copyप्रकारों के बारे में क्या ?

अब, आपने Copyप्रकारों के बारे में पूछा । अधिकांश भाग के लिए ऊपर दिए गए तर्क अभी भी लागू होते हैं। यदि आप एक समारोह है कि, की तरह लिख रहे हैं puts, केवल एक की जरूरत है &A, का उपयोग कर T: Borrow<A>फोन करने वाले के लिए और अधिक लचीला हो सकता है; इस तरह के एक समारोह के wrapलिए पूरी जरूरत है A, यह सिर्फ स्वीकार करने के लिए अधिक लचीला है A। लेकिन स्वीकार करने Copyके एर्गोनोमिक लाभ के प्रकार T: Into<A>बहुत कम स्पष्ट हैं।

  • पूर्णांक प्रकारों के लिए, क्योंकि जेनेरिक प्रकार की गड़बड़ी के साथ गड़बड़ी करते हैं, उनका उपयोग करके आम तौर पर शाब्दिक उपयोग करने के लिए इसे कम एर्गोनोमिक बनाते हैं; आप अंत में स्पष्ट रूप से प्रकार एनोटेट कर सकते हैं।
  • चूंकि &u32यह लागू नहीं होता है Into<u32>, इसलिए यह विशेष चाल यहाँ काम नहीं करेगी।
  • चूंकि Copyप्रकार आसानी से स्वामित्व मूल्यों के रूप में उपलब्ध हैं, इसलिए पहली जगह में संदर्भ द्वारा उनका उपयोग करना कम आम है।
  • अंत में, एक &Aमें बदल रहा है Aजब A: Copyबस जोड़ने के रूप में सरल है *; ज्यादातर मामलों में जेनरिक का उपयोग करने की गयी जटिलता के असंतुलन को रोकने के लिए उस कदम को छोड़ना संभवत: पर्याप्त जीत नहीं है।

अंत में, fooलगभग निश्चित रूप से केवल स्वीकार करना चाहिए value: u32और कॉलर को यह तय करने देना चाहिए कि उस मूल्य को कैसे प्राप्त किया जाए।

यह सभी देखें

  • जब मूल्य के स्वामित्व की विधि की आवश्यकता हो तो क्या यह पास-दर-मूल्य या पास-दर-संदर्भ के लिए अधिक परंपरागत है?
2 Sunreef Aug 18 2020 at 11:00

आपके पास फ़ंक्शन के साथ आप केवल एक u32या एक प्रकार का उपयोग कर सकते हैं जिसे उधार लिया जा सकता है u32

आप दूसरे टेम्पलेट तर्क का उपयोग करके अपने कार्य को अधिक सामान्य बना सकते हैं।

fn foo<T: Copy, N: Borrow<T>>(value: N) -> T {
    *value.borrow()
}

यह हालांकि केवल एक आंशिक समाधान है क्योंकि इसे सही ढंग से काम करने के लिए कुछ मामलों में टाइप एनोटेशन की आवश्यकता होगी।

उदाहरण के लिए, यह बॉक्स से बाहर काम करता है usize:

let v = 0usize;
println!("{}", foo(v));

कंपाइलर को अनुमान लगाने के लिए यहां कोई समस्या नहीं है कि foo(v)usize

हालाँकि, यदि आप प्रयास करते हैं foo(&v), तो संकलक शिकायत करेगा कि यह सही आउटपुट प्रकार नहीं ढूँढ सकता Tक्योंकि विभिन्न प्रकारों के लिए &Tकई Borrowलक्षण लागू कर सकता है । आपको स्पष्ट रूप से निर्दिष्ट करने की आवश्यकता है कि आप आउटपुट के रूप में किसका उपयोग करना चाहते हैं।

let output: usize = foo(&v);