कॉपी प्रकारों के लिए उधार राशि का विरोध?
मैंने उन Borrow
विशेषताओं को परिभाषित करने के लिए उपयोग किया है जो एक स्वामित्व प्रकार या एक संदर्भ, जैसे T
या दोनों को स्वीकार करने वाले कार्यों को परिभाषित करते हैं &T
। borrow()
विधि तो समारोह प्राप्त करने के लिए कहा जाता है &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
दिया गया था? या इस तरह की बात लिखने का मुहावरेदार तरीका ऊपर है?
जवाब
वास्तव में इसके लिए एक उलटा गुण नहीं है 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
और कॉलर को यह तय करने देना चाहिए कि उस मूल्य को कैसे प्राप्त किया जाए।
यह सभी देखें
- जब मूल्य के स्वामित्व की विधि की आवश्यकता हो तो क्या यह पास-दर-मूल्य या पास-दर-संदर्भ के लिए अधिक परंपरागत है?
आपके पास फ़ंक्शन के साथ आप केवल एक 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);