प्रॉम्प्ट और लागू संदर्भ
2021 से 2024 Edition में जाने के बाद एक Rust crate कंपाइल होने में विफल हो जाता है क्योंकि इसका मौजूदा extern ब्लॉक अब स्वीकार्य नहीं है। बताएं कि unsafe extern क्यों आवश्यक है, ABI घोषणा का ऑडिट कैसे करें, और unsafe सीमा को एक सत्यापन योग्य सुरक्षित API में कैसे लपेटें।
Rust 2024 में फॉरेन (foreign) ब्लॉकों के लिए unsafe कीवर्ड का उपयोग करना आवश्यक है। Rust किसी बाहरी लाइब्रेरी द्वारा आपूर्ति किए गए सिग्नेचर, कॉलिंग कन्वेंशन, ग्लोबल वेरिएबल्स, या पॉइंटर कॉन्ट्रैक्ट्स को सिद्ध (prove) नहीं कर सकता है, इसलिए घोषणा लेखक को उन मान्यताओं की ज़िम्मेदारी लेनी होगी।
इंटरव्यूअर क्या मूल्यांकन करता है
इंटरव्यूअर यह देखना चाहता है कि क्या आप एक unsafe घोषणा और हर कॉल साइट पर unsafe को उजागर करने वाले API के बीच अंतर समझते हैं। आपको ABI, इंटीजर विड्थ, लेआउट, शून्य करने योग्य (nullable) पॉइंटर्स, ओनरशिप, थ्रेड बाधाओं, इनिशियलाइज़ेशन, और एक छोटे ऑडिट योग्य FFI मॉड्यूल को कवर करना चाहिए।
स्पष्टीकरण प्रश्न
Crate के Edition, टारगेट प्लेटफ़ॉर्म, फॉरेन ABI, और हेडर वर्ज़न की पुष्टि करें। पूछें कि क्या फ़ंक्शन ओन्ड (owned) रिसोर्स लौटाते हैं, कौन से पॉइंटर्स null हो सकते हैं, उन्हें कौन मुक्त (free) करता है, क्या कॉलबैक थ्रेड्स को क्रॉस करते हैं, और क्या डायनामिक लाइब्रेरी वर्ज़न अलग हो सकते हैं। इन उत्तरों के बिना केवल सिंटैक्स-आधारित समाधान अधूरा है।
30-सेकंड उत्तर ढांचा
"Rust 2024 extern घोषणा को ही unsafe के रूप में चिह्नित करता है क्योंकि कंपाइलर फॉरेन ABI अनुबंध को सत्यापित नहीं कर सकता है। मैं unsafe extern लिखूँगा, कॉलिंग कन्वेंशन, लेआउट, इंटीजर विड्थ, पॉइंटर वैलिडिटी, डिस्ट्रक्शन फ़ंक्शंस और थ्रेड नियमों का ऑडिट करूँगा, और किसी फ़ंक्शन को safe केवल तभी चिह्नित करूँगा जब उसकी पूर्व-शर्तें (preconditions) रैपर द्वारा सिद्ध हो जाएँ। मैं गैर-प्रमाणित शर्तों को unsafe रखूँगा, और फिर क्रॉस-टारगेट बिल्ड और ABI रीग्रेशन्स के साथ माइग्रेशन को मान्य करूँगा।"
चरण-दर-चरण गहन विश्लेषण
चरण 1: unsafe extern की ज़िम्मेदारी स्पष्ट करें
unsafe extern यह बताता है कि एक घोषणा अपरिभाषित व्यवहार (undefined behavior) को सक्षम कर सकती है और घोषणा लेखक इसके अनुबंध के लिए ज़िम्मेदार है। यह C कार्यान्वयन को मान्य नहीं करता है या कॉल करने वालों के लिए तर्कों (arguments) की जांच नहीं करता है। 2024 Edition उस ज़िम्मेदारी को सोर्स कोड में दृश्यमान बनाता है।
चरण 2: ABI और लेआउट का ऑडिट करें
extern "C" जैसे कॉलिंग कन्वेंशन, स्ट्रक्ट लेआउट, एनम प्रतिनिधित्व, अलाइनमेंट, इंटीजर विड्थ और रिटर्न-वैल्यू नियमों की जांच करें। हेडर, जेनरेटेड बाइंडिंग्स, और लिंक्ड लाइब्रेरी को समान वर्ज़न वाले अनुबंध का वर्णन करना चाहिए; स्थानीय स्तर पर सफल रन क्रॉस-टारगेट का प्रमाण नहीं है।
चरण 3: सुरक्षित और असुरक्षित फॉरेन फ़ंक्शंस को अलग करें
फॉरेन ब्लॉक में फ़ंक्शन डिफ़ॉल्ट रूप से unsafe होते हैं। किसी फ़ंक्शन को safe के रूप में घोषित किया जा सकता है जब उसकी सार्वजनिक पूर्व-शर्तें सिद्ध हो चुकी हों; फिर कॉल करने वालों को unsafe ब्लॉक की आवश्यकता नहीं होती है, लेकिन घोषणा लेखक के पास अभी भी उस प्रमाण का स्वामित्व होता है। केवल unsafe सिंटैक्स को कम करने के लिए किसी अज्ञात फ़ंक्शन को safe के रूप में चिह्नित न करें।
unsafe extern "C" {
safe fn library_version() -> u32;
fn library_parse(ptr: *const u8, len: usize) -> i32;
}चरण 4: पॉइंटर्स, ओनरशिप और डिस्ट्रक्शन को रैप करें
एक रॉ पॉइंटर को रेफरेंस में बदलने से पहले, नॉन-नलनेस, अलाइनमेंट, लंबाई और लाइफटाइम को सत्यापित करें। किसी फॉरेन लाइब्रेरी द्वारा लौटाए गए संसाधनों को सामान्यतः उसके संबंधित free फ़ंक्शन द्वारा ही नष्ट किया जाना चाहिए; उस सीमा के पार Rust के डिफ़ॉल्ट डिस्ट्रक्टर या किसी अन्य एलोकेटर का उपयोग नहीं किया जाना चाहिए।
चरण 5: थ्रेड और कॉलबैक बाधाओं की जांच करें
निर्धारित करें कि क्या हैंडल थ्रेड्स को क्रॉस कर सकते हैं, क्या कॉलबैक लाइब्रेरी के स्वामित्व वाले थ्रेड्स पर चलते हैं, क्या कॉलबैक पुनः प्रवेश (re-enter) कर सकते हैं, और क्या डिस्ट्रक्शन कॉलबैक की प्रतीक्षा करता है। यदि उन विशेषताओं की स्थिर रूप से गारंटी नहीं दी जा सकती है, तो रैपर के थ्रेडिंग मॉडल को सीमित करें और एक शटडाउन बैरियर प्रदान करें।
चरण 6: रैपर में सुरक्षा की पूर्व-शर्तें डालें
एक सुरक्षित फ़ंक्शन को ऐसे Rust प्रकारों को स्वीकार करना चाहिए जो इसकी बाधाओं को व्यक्त करते हैं, जैसे कि स्लाइस, एनम, या ओन्ड हैंडल, बजाय इसके कि प्रत्येक कॉलर को रॉ पॉइंटर और लंबाई पास करनी पड़े। जांच को केंद्रीकृत करें, unsafe ऑपरेशन को कुछ पंक्तियों तक सीमित रखें, और प्रत्येक पूर्व-शर्त का दस्तावेजीकरण और परीक्षण करें।
चरण 7: माइग्रेशन टूल्स और मल्टीपल टारगेट्स के साथ मान्य करें
Edition माइग्रेशन जांच और cargo fix --edition चलाएं, फिर जेनरेट किए गए extern परिवर्तनों की मैन्युअल रूप से समीक्षा करें। CI को होस्ट और टारगेट प्लेटफ़ॉर्म, डिबग और रिलीज़ बिल्ड, स्टैटिक और डायनामिक लिंकिंग, और वास्तविक लाइब्रेरी वर्ज़न के विरुद्ध ABI रीग्रेशन्स को कवर करना चाहिए।
चरण 8: वर्ज़न ड्रिफ्ट और रोलबैक को संभालें
यदि हेडर, बाइंडिंग और डायनामिक लाइब्रेरी असहमत हैं, तो कास्ट के साथ बेमेल को छिपाने के बजाय वर्ज़न पिन करें या बाइंडिंग को फिर से जेनरेट करें। चरणों में रोल आउट करें, एक रोलबैक पथ सुरक्षित रखें, और लोड विफलताओं, एरर कोड परिवर्तनों और रिसोर्स लीक की निगरानी करें।
उच्च-गुणवत्ता वाला नमूना उत्तर
मैं घोषणा समीक्षा को कॉल-साइट इनकैप्सुलेशन से अलग करूँगा। सबसे पहले, प्रत्येक फॉरेन ब्लॉक को unsafe extern "C" में बदलें और सटीक हेडर और लाइब्रेरी वर्ज़न के साथ ABI, लेआउट, नलेबिलिटी, ओनरशिप और फ्री फ़ंक्शंस की तुलना करें। केवल सिद्ध सार्वजनिक पूर्व-शर्तों वाले फ़ंक्शंस को ही safe के रूप में चिह्नित करें। फिर रॉ पॉइंटर्स को एक Rust हैंडल और स्लाइस-आधारित FFI मॉड्यूल के पीछे रखें जो लंबाई, इनिशियलाइज़ेशन, थ्रेडिंग और कॉलबैक शटडाउन की जांच करता है, और संसाधनों को हमेशा लाइब्रेरी के फ़ंक्शन से मुक्त करता है। अंत में, यांत्रिक परिवर्तनों के लिए cargo fix --edition का उपयोग करें, diff की समीक्षा करें, और कई टारगेट्स पर ABI, एरर-पाथ, समवर्ती-शटडाउन और डायनामिक-लाइब्रेरी संगतता परीक्षण चलाएं। यह प्रत्येक कॉलर को फॉरेन लाइब्रेरी के छिपे हुए अनुबंध को पुन: उत्पन्न करने के लिए मजबूर किए बिना unsafe को दृश्यमान और ऑडिट योग्य बनाए रखता है।
सामान्य गलतियाँ
extern में केवल unsafe जोड़ना और वहीं रुक जाना
यह केवल सिंटैक्स को ठीक करता है। गलत सिग्नेचर, लेआउट या डिस्ट्रक्शन प्रोटोकॉल अभी भी अपरिभाषित व्यवहार का कारण बन सकते हैं, इसलिए अनुबंध समीक्षा और रनटाइम रीग्रेशन्स आवश्यक बने रहते हैं।
प्रत्येक फॉरेन फ़ंक्शन को safe के रूप में चिह्नित करना
safe कॉल करने वालों के लिए एक गारंटी है, कंपाइलर के लिए कोई संकेत नहीं। इसका उपयोग केवल तभी करें जब रैपर और प्रकार लगातार पूर्व-शर्तों को लागू करते हों; अज्ञात या विश्व स्तर पर स्टेटफुल फ़ंक्शंस को unsafe ही रहना चाहिए।
C संसाधन को Rust के Box से मुक्त करना
क्रॉस-एलोकेटर डिस्ट्रक्शन हीप को दूषित कर सकता है। निर्माण करने वाली लाइब्रेरी को ही फ्री फ़ंक्शन प्रदान करना चाहिए, और रैपर के Drop कार्यान्वयन को इसे सही शटडाउन क्रम में कॉल करना चाहिए।
फॉलो-अप प्रश्न और उत्तर
क्या होगा यदि C हेडर स्ट्रक्ट लेआउट का दस्तावेजीकरण नहीं करता है?
लेआउट को एक असत्यापित अनुबंध के रूप में मानें। आधिकारिक बाइंडिंग्स या अपारदर्शी (opaque) हैंडल को प्राथमिकता दें। यदि किसी स्ट्रक्ट को सीमा पार करनी ही है, तो फ़ील्ड्स का अनुमान लगाने के बजाय कंपाइलर, प्लेटफ़ॉर्म और लाइब्रेरी वर्ज़न को पिन करें और आकार, अलाइनमेंट और एंड-टू-एंड व्यवहार को सत्यापित करें।
क्या होगा यदि एक सुरक्षित घोषणा के बाद लाइब्रेरी अपग्रेड व्यवहार बदल देता है?
प्रत्येक वर्ज़न अपग्रेड के लिए सुरक्षित गारंटी का पुनः ऑडिट करें। लाइब्रेरी और बाइंडिंग वर्ज़न को पिन करें और एरर कोड, थ्रेडिंग और रिसोर्स सिमेंटिक्स के लिए संगतता परीक्षण जोड़ें। यदि वही पूर्व-शर्तें अब लागू नहीं होती हैं, तो safe को हटा दें और रैपर में स्पष्ट रूप से स्थिति को संभालें।
एक Rust API को उन कॉलबैक को कैसे संभालना चाहिए जो किसी भी थ्रेड पर चल सकते हैं?
शेयर किए गए म्यूटेबल स्टेट वाले किसी भी मनमाने Rust क्लोजर को सीधे C में पास न करें। थ्रेड-सेफ चैनल या नियंत्रित एक्ज़ीक्यूटर का उपयोग करें, कॉलबैक लाइफटाइम और शटडाउन बैरियर को परिभाषित करें, और एक सुरक्षित इंटरफ़ेस केवल तभी प्रदर्शित करें जब Send, सिंक्रोनाइज़ेशन और लाइफटाइम आवश्यकताओं को लागू किया गया हो।