क्यों Rust दो mutable संदर्भों वाले कोड को अनुकूलित नहीं करता? शुरुआती लोगों के लिए विश्लेषण

शुरुआती लोगों के लिए ऑनलाइन Python प्रशिक्षक

Python आसानी से सीखें बिना सिद्धांत के बोझ के। स्वचालित जांच के साथ व्यावहारिक कार्य हल करें और ब्राउज़र में सीधे कोड लिखें।

पाठ्यक्रम शुरू करें

परिचय: Rust कंपाइलर की पहेली

\n\n

कल्पना करें: आप Rust में लिख रहे हैं, आप मेमोरी सुरक्षा के सच्चे प्रशंसक हैं। आप जानते हैं कि एक ही डेटा पर दो mutable reference (&mut T) कभी भी एक साथ मौजूद नहीं हो सकते। यह उधार लेने के नियमों (borrowing rules) में से एक मुख्य नियम है। Rust कंपाइलर इस पर सख्ती से नज़र रखता है।

\n\n

ऐसा लगता है कि यदि कंपाइलर आश्वस्त है कि दो &mut ओवरलैप नहीं होते (alias नहीं हैं), तो उसे इस ज्ञान का उपयोग आक्रामक अनुकूलन के लिए करना चाहिए। उदाहरण के लिए, संचालन को पुनर्व्यवस्थित करना, रजिस्टरों में मानों को कैश करना, मेमोरी को दोबारा न पढ़ना। लेकिन यहाँ विरोधाभास है: Rust कंपाइलर (LLVM) अक्सर ऐसा नहीं करता। क्यों? आखिर यह बहुत तार्किक लगता है!

\n\n

इस प्रश्न ने Stack Overflow पर हज़ारों बार देखा गया है। और आज, हम सच्चे जासूसों की तरह, इस पहेली को सुलझाएंगे। हम केवल उत्तर नहीं पढ़ेंगे - हम गहरे कारणों को समझेंगे, जो C विरासत, LLVM और अनुकूलन के काम करने के तरीके से संबंधित हैं। चलिए शुरू करते हैं!

\n\n

1. Rust का सुनहरा नियम: दो &mut हमेशा अलग डेटा होते हैं

\n\n

आइए मूल बातें याद करें। Rust में दो प्रकार के संदर्भ होते हैं:

\n
    \n
  • अपरिवर्तनीय (&T): डेटा की कोई भी संख्या हो सकती है, लेकिन उन्हें बदला नहीं जा सकता।
  • \n
  • परिवर्तनीय (&mut T): केवल एक डेटा हो सकता है, लेकिन उसे बदला जा सकता है।
  • \n
\n\n

यह नियम &mut के लिए noalias है। कंपाइलर, सिद्धांत रूप में, मान सकता है: "अगर मैं दो &mut i32 देखता हूँ, तो वे निश्चित रूप से मेमोरी में अलग-अलग स्थानों की ओर इशारा करते हैं।"

\n\n

लेकिन आइए एक सरल उदाहरण देखें जो समस्या को दर्शाता है:

\n\n
fn example(x: &mut i32, y: &mut i32) -> i32 {\n    *x = 42;\n    *y = 10;\n    *x // हम 42 की उम्मीद करते हैं, लेकिन...\n}
\n\n

यहाँ सब कुछ स्पष्ट है: x और y अलग-अलग चर हैं। Rust गारंटी देता है कि वे ओवरलैप नहीं होते। कंपाइलर असाइनमेंट किए बिना केवल 42 लौटा सकता था। लेकिन क्या वास्तव में ऐसा है?

\n\n

2. समस्या: LLVM और Rust की गारंटियों के प्रति इसका "अंधापन"

\n\n

हमारी कहानी का मुख्य पात्र LLVM (Low Level Virtual Machine) है। Rust मशीन कोड उत्पन्न करने के लिए बैकएंड के रूप में LLVM का उपयोग करता है। LLVM एक शक्तिशाली ऑप्टिमाइज़र है, लेकिन यह Rust के नियमों को नहीं जानता। यह केवल C/C++ के नियमों को जानता है।

\n\n

C/C++ में एक कीवर्ड restrict (C99 में) है, जो कंपाइलर को बताता है: "यह संदर्भ अद्वितीय है, कोई और इस डेटा की ओर इशारा नहीं करता।" लेकिन C/C++ में restrict प्रोग्रामर का वादा है, कंपाइलर की गारंटी नहीं। यदि प्रोग्रामर वादा तोड़ता है - तो undefined behavior होता है।

\n\n

Rust पक्की गारंटी देता है। लेकिन इस गारंटी को LLVM तक पहुँचाने के लिए, Rust फ्रंटएंड को LLVM IR (मध्यवर्ती प्रतिनिधित्व) में &mut को noalias विशेषता के साथ स्पष्ट रूप से चिह्नित करना होगा।

\n\n

और यहाँ सबसे दिलचस्प शुरू होता है। Rust &mut को केवल कुछ मामलों में noalias के रूप में चिह्नित करता है। और कुछ में - नहीं। क्यों?

\n\n

2.1. ऐतिहासिक समस्या: UnsafeCell और Unsafe कोड

\n\n

Rust के अंदर एक प्रकार UnsafeCell<T> है। यह Cell, RefCell, Mutex और अन्य प्रकारों के लिए बिल्डिंग ब्लॉक है, जो आंतरिक परिवर्तनशीलता (interior mutability) की अनुमति देते हैं।

\n\n

यदि आपके पास &mut UnsafeCell<i32> है, तो इसके माध्यम से एक कच्चा पॉइंटर प्राप्त किया जा सकता है और अपरिवर्तनीय संदर्भ के माध्यम से भी डेटा बदला जा सकता है। यह noalias गारंटी को तोड़ता है!

\n\n

undefined behavior से बचने के लिए, Rust कंपाइलर &mut पर noalias नहीं लगाता है, यदि इसके पीछे UnsafeCell छिपा हो सकता है। लेकिन समस्या इससे गहरी है।

\n\n

3. वास्तविक उदाहरण: अनुकूलन क्यों काम नहीं करता

\n\n

आइए example फ़ंक्शन पर वापस आते हैं

ब्लॉग

पुस्तक अनुशंसाएँ