کیا آپ کو وہ احساس معلوم ہے جب آپ کوئی نئی زبان سیکھنا شروع کرتے ہیں اور توقع کرتے ہیں کہ آپ تیزی سے دوڑیں گے، لیکن اس کے بجائے آپ پہلے ہی قدم پر ٹھوکر کھا جاتے ہیں؟ Rust کے ساتھ میرا یہی حال تھا۔ میرے پاس ایک بالکل ٹھیک JavaScript utility تھی — کچھ ایسی جو چند arrays کو parse اور mutate کرتی تھی — اور میں نے سوچا کہ میں اسے کافی کے ایک وقفے میں دوبارہ لکھ لوں گا۔ میں نے اپنا editor کھولا، وہ کوڈ ٹائپ کیا جو مجھے فطری لگا، اور compiler چلا دیا۔ وہ پھٹ پڑا۔ syntax errors کی وجہ سے نہیں۔ یا کسی مسنگ semicolon کی وجہ سے نہیں۔ اس نے مجھے بتایا کہ میں نے ایسی value استعمال کرنے کی کوشش کی ہے جو پہلے ہی 'move' ہو چکی ہے۔ میں اسکرین کو گھورتا رہا۔ Move؟ میں نے تو کچھ move نہیں کیا تھا۔ میں نے تو بس اسے ایک دوسرے variable کو assign کر دیا تھا۔

JavaScript کا آرام دہ حلقہ (Comfort Zone)

JavaScript آپ کو ڈیٹا کے ساتھ ایک مشترکہ وائٹ بورڈ کی طرح پیش آنے کی تربیت دیتی ہے۔ آپ ایک object یا array declare کرتے ہیں، اسے کسی function کو دیتے ہیں، اس function کو ایک property push کرنے دیتے ہیں، اور پھر اسے کہیں اور بھیج دیتے ہیں۔ Garbage collector پس منظر میں ایک غیر مرئی (invisible) خاکروب کی طرح بیٹھا رہتا ہے، جو اس چیز کو صاف کرنے کا انتظار کرتا ہے جسے آپ پیچھے چھوڑ دیتے ہیں۔ آپ کبھی نہیں پوچھتے، "اس string کا مالک کون ہے؟" آپ پوچھتے ہیں، "کیا میں اسے پڑھ سکتا ہوں؟" اگر جواب ہاں ہے، تو آپ اسے استعمال کرتے ہیں۔ اگر آپ کو ایک اور reference کی ضرورت ہے، تو آپ بس let b = a کہتے ہیں اور آگے بڑھ جاتے ہیں۔ دونوں variables میموری کے ایک ہی حصے کی طرف اشارہ کرتے ہیں، اور اگر a اسے mutate کرتا ہے، تو b کو تبدیلی فوری طور پر نظر آتی ہے۔ یہ آسان ہے۔ یہ افراتفری کا باعث بھی ہے، لیکن یہ افراتفری آپ کو شاذ و نادر ہی نقصان پہنچاتی ہے کیونکہ runtime صفائی کا کام خود سنبھال لیتا ہے۔

پہلا Compiler Shock

Rust آپ پر بھروسہ نہیں کرتا۔ یہ سننے میں سخت لگتا ہے، لیکن یہ وہ پہلی چیز ہے جو آپ سیکھتے ہیں۔ یہ زبان تین سخت اصولوں پر مبنی ownership system کے گرد بنی ہے۔ پہلا، ہر value کا بالکل ایک ہی مالک ہوتا ہے۔ دوسرا، ایک وقت میں صرف ایک ہی مالک ہوتا ہے۔ تیسرا، جب وہ مالک scope سے باہر جاتا ہے، تو Rust خود بخود اس value کو drop کر دیتا ہے۔ کوئی garbage collector نہیں۔ پس منظر میں کوئی reference counting نہیں، جب تک کہ آپ واضح طور پر اسے منتخب نہ کریں۔ بس یہ تین اصول، جو آپ کا کوڈ چلنے سے پہلے compiler کے ذریعے نافذ کیے جاتے ہیں۔

جب میں نے اس JavaScript utility کا Rust ورژن لکھا، تو میں نے وہی کیا جو مجھے فطری لگا۔ میں نے ایک vector بنایا، جو array کا Rust ورژن ہے، اور اسے دوسرے variable کو assign کر دیا۔ پھر میں نے پہلے والے کو استعمال کرنے کی کوشش کی۔ Compiler نے انکار کر دیا۔ JavaScript میں، let b = a reference کی کاپی کرتا ہے۔ Rust میں، یہ ownership کو move کر دیتا ہے۔ اصل variable غیر معتبر (invalid) ہو جاتا ہے۔ Compiler ایسا اس بات کی ضمانت دینے کے لیے کرتا ہے کہ آپ کے پاس غلطی سے دو راستے ایک ہی میموری پر اثر انداز نہ ہوں، کیونکہ دوسرے سسٹمز میں data races اور use-after-free bugs اسی طرح پیدا ہوتے ہیں۔

Rust آپ کے کھلونے کیوں چھین لیتا ہے

JavaScript میں اس پیٹرن کو دیکھیں:

let a = [1, 2, 3];
let b = a;
a.push(4);
// Both a and b see 4.

یہ ایک فطری عمل ہے۔ آپ اس بارے میں دو بار نہیں سوچیں گے۔ Rust میں، وہی وجدان (intuition) آپ کے ٹائپنگ مکمل کرنے سے پہلے ہی compiler کی طرف سے مسترد کر دیا جائے گا۔ ایک بار جب b vector کا مالک بن جاتا ہے، تو a محض ایک خالی نام رہ جاتا ہے۔ آپ اس میں کچھ push نہیں کر سکتے۔ آپ اسے دیکھ بھی نہیں سکتے۔ میموری اب b کی ملکیت ہے۔

یہ ایک سزا کی طرح محسوس ہوتا ہے جب تک کہ آپ کو یہ احساس نہ ہو جائے کہ یہ کس چیز کو روکتا ہے۔ اگر دو variables بغیر کسی ہم آہنگی کے ایک ہی heap data کو mutate کر سکتے، تو آپ میموری کرپشن (memory corruption) کا خطرہ مول لے رہے ہوتے۔ Rust اس عمل کو واضح بنا کر غلطیوں (bugs) کی اس پوری قسم کو ختم کر دیتا ہے۔ Compiler بحث نہیں کر رہا۔ وہ ایک گیٹ کیپر (gatekeeper) کا کردار ادا کر رہا ہے۔ یہ آپ کو ہر قدم پر فیصلہ کرنے پر مجبور کرتا ہے کہ آپ کے کوڈ کا کون سا حصہ کس ڈیٹا کا ذمہ دار ہے۔

Borrowing: ایک ایسا حل جو درحقیقت ایک فیچر ہے

یقیناً، اگر ہر assignment ہمیشہ کے لیے ownership منتقل کر دیتا، تو پروگرام لکھنا تھکا دینے والا کام ہوتا۔ آپ کو ہر چیز کو clone کرنا پڑتا، جس سے میموری اور رفتار ضائع ہوتی۔ Rust اس مسئلے کو borrowing کے ذریعے حل کرتا ہے۔

Borrowing آپ کو کسی value کو لیے بغیر استعمال کرنے کی اجازت دیتی ہے۔ اس کی دو اقسام ہیں، اور ان کا فرق اہم ہے۔

  • Immutable borrows, جنہیں &T لکھا جاتا ہے: یہ آپ کو ڈیٹا پڑھنے کی اجازت دیتے ہیں۔ آپ جتنے چاہیں اتنے حاصل کر سکتے ہیں...