प्रॉम्प्ट और दायरा
आप विभिन्न निष्पादकों द्वारा उपयोग किए जाने वाले Service trait का रखरखाव करते हैं। इसके मेथड्स async fn का उपयोग करते हैं, लेकिन केवल कुछ कॉल साइट्स ही एक Future को ऐसे टास्क में पास करती हैं जो थ्रेड्स के बीच स्थानांतरित (move) हो सकता है। बताएं कि प्रत्येक लौटाए गए Future को Send के रूप में घोषित करना अत्यधिक प्रतिबंधात्मक क्यों है, और रिटर्न टाइप नोटेशन (RTN), एक trait वैरिएंट और एक GAT के लिए चयन नियम प्रदान करें।
यह प्रश्न Rust 1.75 में स्थिर किए गए AFIT और RPITIT तथा RFC 3654 द्वारा प्रस्तावित RTN को लक्षित करता है। RTN अभी भी एक nightly प्रयोग है; अपने उत्तर में भाषा की क्षमता, निष्पादक की आवश्यकताओं और रिलीज़ स्थिरता को अलग-अलग स्पष्ट करें।
साक्षात्कारकर्ता क्या जांच रहा है
एक सशक्त उत्तर प्रत्येक Future को एक trait मेथड के अज्ञात रिटर्न टाइप के रूप में पहचानता है। जेनेरिक या dyn Trait कॉलर्स केवल trait से इसके Send गुण का अनुमान नहीं लगा सकते हैं, इसलिए यह बाउंड उस कॉल साइट पर होना चाहिए जो वास्तव में थ्रेड्स को पार करती है।
साक्षात्कारकर्ता RTN के दायरे की भी अपेक्षा करता है: यह एक सामान्य फ़ील्ड प्रकार के रूप में कार्य करने के बजाय AFIT/RPITIT रिटर्न मानों को बाध्य करता है। Nightly जोखिम, सिंगल-थ्रेडेड निष्पादक, वर्क-स्टीलिंग निष्पादक और अनुकूलता पथों पर चर्चा करें।
स्पष्टीकरण हेतु प्रश्न
क्या Future थ्रेड्स के बीच स्थानांतरित हो सकता है
यदि निष्पादक काम को एक ही थ्रेड पर पिन करता है, तो Send अनावश्यक हो सकता है। एक वर्क-स्टीलिंग निष्पादक को स्पॉन किए गए Future के Send होने की आवश्यकता होती है, और आमतौर पर 'static की भी आवश्यकता होती है।
क्या बाउंड एक मेथड के लिए है या पूरे trait के लिए
यदि केवल call को स्थानांतरित करने योग्य होना चाहिए, तो RTN call(..): Send को व्यक्त कर सकता है। यदि प्रत्येक मेथड को समान बाउंड की आवश्यकता है, तो एक trait वैरिएंट मेथड-स्तरीय क्लॉज़ को दोहराने से बचाता है।
क्या प्रोडक्शन में nightly का उपयोग किया जा सकता है
यदि एक सार्वजनिक लाइब्रेरी को स्थिर Rust का समर्थन करना चाहिए, तो RTN एकमात्र डिज़ाइन नहीं हो सकता है। एक जनरेट किए गए Send trait या एक स्पष्ट GAT का मूल्यांकन करें और रिलीज़ मैट्रिक्स में कंपाइलर वर्ज़न रिकॉर्ड करें।
30-सेकंड का उत्तर
“मैं पहले यह जांचता हूं कि क्या निष्पादक टास्क को स्थानांतरित कर सकता है और किन मेथड्स को उस गारंटी की आवश्यकता है। मैं बेस trait को न्यूनतम रूप से बाध्य रखता हूं, फिर उस कॉल साइट पर T::call(..): Send + 'static लिखता हूं जो थ्रेड्स के पार स्पॉन करती है। यदि प्रत्येक मेथड को Send की आवश्यकता है, तो मैं एक trait वैरिएंट का उपयोग करता हूं; यदि स्थिर Rust और एक नामित Future की आवश्यकता है, तो मैं एक GAT का उपयोग करता हूं। RTN अभी nightly है, इसलिए मैं इसे एक अलग टूलचेन में टेस्ट करूंगा और एक स्थिर फ़ॉलबैक बनाए रखूंगा।”
चरण-दर-चरण समाधान
चरण 1: निष्पादक से बाउंड्स प्राप्त करें
एक सिंगल-थ्रेडेड या थ्रेड-पर-कोर निष्पादक आमतौर पर थ्रेड्स के बीच टास्क को स्थानांतरित नहीं करता है। एक वर्क-स्टीलिंग निष्पादक ऐसा करता है, इसलिए spawn को पास किए गए Future को सामान्यतः Send और अक्सर 'static की आवश्यकता होती है। इसलिए Send उपभोक्ता की आवश्यकता है, न कि प्रत्येक trait मेथड की डिफ़ॉल्ट विशेषता।
चरण 2: RTN के साथ एक मेथड को बाध्य करें
RTN किसी मेथड के लौटाए गए प्रकार में बाउंड्स जोड़ता है। निम्नलिखित कोड nightly सिंटैक्स का उपयोग करता है:
trait Service<Request> {
type Response;
async fn call(&self, request: Request) -> Self::Response;
}
async fn spawn_call<S, R>(service: S, request: R) -> S::Response
where
S: Service<R> + Send + 'static,
R: Send + 'static,
S::call(..): Send + 'static,
{
tokio::spawn(async move { service.call(request).await })
.await
.expect("task failed")
}S::call(..) trait मेथड द्वारा लौटाए गए Future को संदर्भित करता है, न कि उसका इंतज़ार (await) करने के बाद उत्पादित मान को। यह केवल उन उपभोक्ताओं को बाध्य करता है जिन्हें इस विशेषता की आवश्यकता होती है और स्थानीय कार्यान्वयन को अन्य स्थानों पर उपयोग करने योग्य छोड़ देता है।
चरण 3: trait वैरिएंट की तुलना करें
जब प्रत्येक async मेथड को Send की आवश्यकता होती है, तो न्यूनतम रूप से बाध्य बेस trait रखें और एक Send वैरिएंट जनरेट करें:
#[trait_variant::make(SendService: Send)]
trait LocalService<R> {
async fn call(&self, request: R) -> Response;
}यह एक सामान्य पथ वाले सार्वजनिक API के लिए उपयुक्त है: कार्यान्वयनकर्ता एक बेस trait को लक्षित करते हैं और कॉलर्स स्थानीय या Send वैरिएंट चुनते हैं। इसका नुकसान मेथड-स्तरीय सटीकता का कम होना है; जब केवल एक मेथड को बाउंड की आवश्यकता होती है तो RTN बेहतर होता है।
चरण 4: जब स्थिर नामकरण महत्वपूर्ण हो तो GAT चुनें
यदि स्थिर Rust में लौटाए गए Future का नामकरण आवश्यक है, तो एक GAT संबद्ध प्रकार (associated type) को प्रदर्शित कर सकता है:
trait StableService {
type Future<'a>: Future<Output = Response> + Send + 'a
where
Self: 'a;
fn call(&self) -> Self::Future<'_>;
}प्रत्येक कार्यान्वयन को एक ठोस Future प्रकार प्रदान करना होगा, जिससे बॉयलरप्लेट बढ़ता है। इसका उपयोग तब करें जब स्थिर समर्थन और Future प्रकार को संग्रहीत या पुन: उपयोग करना async सिंटैक्स की एर्गोनॉमिक्स से अधिक महत्वपूर्ण हो।
चरण 5: सीमाओं और माइग्रेशन जोखिमों को सत्यापित करें
Rust टीम यह दस्तावेज़ित करती है कि RTN trait से जुड़े फ़ंक्शनों या AFIT/RPITIT का उपयोग करने वाले मेथड्स पर लागू होता है; इसे वर्तमान में struct फ़ील्ड प्रकार के रूप में उपयोग नहीं किया जा सकता है। Nightly CI में सिंटैक्स, डायग्नोस्टिक्स और मैक्रो विस्तार का परीक्षण करें, जबकि स्थिर रिलीज़ के लिए एक trait-वैरिएंट या GAT पथ बनाए रखें।
चरण 6: तुलना को एक नियम में बदलें
मेथड-स्तरीय, कॉल-साइट-स्तरीय क्रॉस-थ्रेड आवश्यकता के लिए RTN चुनें; जब सभी मेथड्स समान आवश्यकता साझा करते हैं तो एक trait वैरिएंट चुनें; और जब स्थिर Rust और एक नामकरण योग्य रिटर्न प्रकार अनिवार्य हो तो एक GAT चुनें। यदि निष्पादक कभी किसी टास्क को स्थानांतरित नहीं करता है, तो कथित सुरक्षा के लिए कार्यान्वयन सेट को कम करने के बजाय स्थानीय Future बनाए रखें।
मॉडल उच्च-गुणवत्ता वाला उत्तर
मैं पहले पूछता हूं कि क्या निष्पादक किसी टास्क को स्थानांतरित कर सकता है। Tokio जैसे वर्क-स्टीलिंग निष्पादक के साथ, स्पॉन किए गए बाहरी Future को आमतौर पर Send + 'static की आवश्यकता होती है; सिंगल-थ्रेडेड निष्पादक के साथ, उस बाउंड को प्रत्येक कार्यान्वयन में प्रचारित करना अनावश्यक है। मैं बेस trait को न्यूनतम रखता हूं और S::call(..): Send + 'static केवल उस जेनेरिक फ़ंक्शन में लिखता हूं जो थ्रेड्स को पार करता है। यह उन कार्यान्वयनों को सुरक्षित रखता है जिनका call Future केवल स्थानीय है।
यदि प्रत्येक async मेथड को स्थानांतरित करने योग्य होना चाहिए, तो मैं Send API प्रदान करने के लिए एक trait वैरिएंट का उपयोग करता हूं। यदि प्रोजेक्ट को स्थिर Rust पर रहना चाहिए और Future को नाम देने या संग्रहीत करने की आवश्यकता है, तो मैं एक GAT का उपयोग करता हूं। RTN अभी भी nightly है, इसलिए मैं इसे CI में अलग रखता हूं और प्रायोगिक सिंटैक्स को सार्वजनिक न्यूनतम वर्ज़न बनाने के बजाय स्थिर डिज़ाइन बनाए रखता हूं।
सामान्य गलतियां
- लक्षण → Trait में प्रत्येक async रिटर्न में
Sendजोड़ना → यह विफल क्यों होता है → मान्य सिंगल-थ्रेडेड कार्यान्वयन बाहर हो जाते हैं → सुधार → क्रॉस-थ्रेड उपभोक्ता पर बाउंड लगाएं। - लक्षण →
S::call(..)को प्रतीक्षित (awaited) परिणाम प्रकार के रूप में मानना → यह विफल क्यों होता है → RTN लौटाए गए Future को बाध्य करता है, न किS::Responseको → सुधार → अंतर को स्पष्ट रूप से बताएं। - लक्षण → Nightly RTN को सीधे प्रोडक्शन में भेजना → यह विफल क्यों होता है → कंपाइलर और सिंटैक्स समर्थन बदल सकता है → सुधार → CI में nightly वर्ज़न पिन करें और एक स्थिर फ़ॉलबैक बनाए रखें।
- लक्षण → प्रत्येक async trait को GAT में बदलना → यह विफल क्यों होता है → कार्यान्वयनकर्ताओं को ठोस Future प्रकार प्रदर्शित करने होंगे → सुधार → GAT का उपयोग केवल तभी करें जब स्थिर नामकरण एक वास्तविक आवश्यकता हो।
- लक्षण → केवल यह जांचना कि क्या
ServiceSendहै → यह विफल क्यों होता है → एक स्थानांतरित करने योग्य सर्विस अभी भी एक गैर-Send Future लौटा सकती है → सुधार → ऑब्जेक्ट, तर्कों (arguments), लौटाए गए Future और बाहरी टास्क को अलग-अलग बाध्य करें।
फॉलो-अप प्रश्न और उत्तर
फॉलो-अप 1: S::call(..): Send के लिए S: Send पर्याप्त क्यों नहीं है?
S: Send बताता है कि सर्विस मान थ्रेड्स के बीच स्थानांतरित हो सकता है। इसका मेथड अभी भी Future में Rc या किसी अन्य गैर-Send मान को कैप्चर कर सकता है। एक क्रॉस-थ्रेड स्पॉन बाहरी Future और प्रत्येक प्रतीक्षित Future की जांच करता है, इसलिए लौटाए गए Future को अपने स्वयं के बाउंड की आवश्यकता होती है।
फॉलो-अप 2: केवल put को Send की आवश्यकता है जबकि get एक स्थानीय कैश का उपयोग करता है। आप क्या करेंगे?
बेस trait को बनाए रखें और क्रॉस-थ्रेड कॉल साइट पर Backend के put(..): Send को बाध्य करें। ऐसा वैरिएंट उत्पन्न न करें जो get को भी प्रतिबंधित करता हो, क्योंकि वह एक मान्य स्थानीय-कैश कार्यान्वयन को अस्वीकार कर देगा।
फॉलो-अप 3: RTN अभी फ़ील्ड प्रकार नहीं हो सकता है। आप Future को कैसे संग्रहीत कर सकते हैं?
एक स्पष्ट GAT, एक नामित Future का उपयोग करें, या सीमा पर इसे Box::pin के साथ मिटा (erase) दें। स्थिर समर्थन, आवंटन लागत और ऑब्जेक्ट-सुरक्षा आवश्यकताओं के आधार पर चयन करें; RTN स्वचालित रूप से एक संग्रहणीय प्रकार नहीं बनाता है।
फॉलो-अप 4: आप कैसे साबित करेंगे कि बाउंड्स बहुत मजबूत नहीं हैं?
तीन कंपाइल केस लिखें: एक स्थानीय कार्यान्वयन जिसमें Send का अभाव है लेकिन वह एक थ्रेड पर चलता है; एक कार्यान्वयन जहां केवल put एक Send Future लौटाता है; और एक जहां प्रत्येक मेथड को थ्रेड्स के पार स्पॉन किया जा सकता है। प्रत्येक को स्थिर और nightly पर चलाएं और सत्यापित करें कि विफलताएं केवल लक्षित कॉल साइट पर ही होती हैं।