لماذا يحتاج Rust إلى تحديد زمن الحياة بشكل صريح؟ نشرح بالأمثلة

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

لماذا يجبرك Rust على تحديد زمن الحياة؟ وكيف يبسط ذلك الحياة؟

\n\n

إذا كنت تدرس Rust، فستواجه عاجلاً أم آجلاً هذا التركيب الغامض: 'a, 'b... ويطرح سؤال منطقي: «لماذا يجب عليّ تحديد زمن الحياة بشكل صريح؟ اللغات الأخرى تستغني عن ذلك!»

\n\n

هذا السؤال هو من أكثر الأسئلة شيوعاً على Stack Overflow (آلاف المشاهدات). وليس من المستغرب: مفهوم lifetimes في Rust يبدو معقداً حتى تفهم جوهره. دعنا نفهمه بدون نظرية مملة، من خلال أمثلة حية.

\n\n

إجابة مختصرة: الأمان بدون جامع القمامة

\n\n

يضمن Rust أمان الذاكرة في وقت الترجمة. يحتاج إلى معرفة بالضبط كم من الوقت تعيش المراجع، لمنع المراجع المعلقة (dangling references). في Java أو Go، يتولى جامع القمامة هذه المهمة. في C++، أنت المسؤول عن الذاكرة (وغالباً ما تخطئ). اختار Rust طريقاً ثالثاً: يتحقق المترجم من كل شيء بشكل ثابت.

\n\n

ولكن لماذا لا يستطيع المترجم التخمين بنفسه؟ يمكنه ذلك في 80% من الحالات. وهذا ما يسمى lifetime elision (قواعد الاستنتاج الضمني). ومع ذلك، في الحالات المعقدة يحتاج إلى مساعدتك.

\n\n

القاعدة رقم 1: المترجم ليس عرافاً

\n\n

انظر إلى هذه الدالة:

\n\n
fn longest(x: &str, y: &str) -> &str {\n    if x.len() > y.len() { x } else { y }\n}
\n\n

للوهلة الأولى، كل شيء منطقي. لكن Rust سيعطي خطأ: «missing lifetime specifier». لماذا؟

\n\n

تخيل أنك مررت x - مرجعاً لسلسلة تعيش لمدة 5 ثوانٍ، و y - مرجعاً لسلسلة تعيش لمدة ثانية واحدة. قد تعيد الدالة أي منهما. ما هو زمن حياة المرجع المُعاد؟ المترجم لا يعرف!

\n\n

الحل - أن نقول صراحة: «المرجع المُعاد سيعيش بنفس مدة كلا المرجعين المُدخلين»:

\n\n
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {\n    if x.len() > y.len() { x } else { y }\n}
\n\n

الرمز 'a هو تعليق توضيحي لزمن الحياة. نقول: «يجب أن يكون لجميع المراجع الثلاثة نفس الحد الأدنى لعمر الوجود». الآن يمكن للمترجم التحقق من أن الكود المُستدعي لن يدمر البيانات قبل الأوان.

\n\n

القاعدة رقم 2: المراجع المختلفة - أعمار مختلفة

\n\n

خطأ شائع للمبتدئين هو الاعتقاد بأن جميع 'a يجب أن تكون متطابقة. في الواقع يمكنك تحديد أعمار مختلفة:

\n\n
fn example<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {\n    x // حسنًا، نعيد مرجعًا بعمر 'a\n    // y // خطأ! y يعيش 'b، ونحن وعدنا بـ 'a\n}
\n\n

هنا نقول صراحة: يمكن أن يكون لـ x و y عمر مختلف. لكن يمكننا فقط إعادة المرجع الذي يتطابق عمره مع العمر الموعود في نوع الإرجاع.

\n\n

القاعدة رقم 3: الهياكل يمكنها أيضًا تخزين المراجع

\n\n

عندما تنشئ هيكلاً يحتوي على مراجع، يجب تحديد زمن الحياة في Rust بشكل إلزامي:

\n\n
struct Book<'a> {\n    title: &'a str,\n    author: &'a str,\n}\n\nfn main() {\n    let title = String::from("Rust Book");\n    let book;\n    {\n        let author = String::from("John Doe");\n        book = Book { title: &title, author: &author };\n    } // author يتم تدميره هنا!\n    println!("{}", book.title); // خطأ! book.author معلق\n}
\n\n

لن يسمح المترجم بترجمة هذا الكود، لأن book.author يشير إلى سلسلة تم حذفها بالفعل. lifetimes الصريحة في Rust تسمح باكتشاف مثل هذه الأخطاء في وقت الترجمة، وليس في وقت التشغيل.

\n\n

القاعدة رقم 4: Lifetime Elision - متى يمكنك عدم الكتابة

\n\n

Rust ذكي بما يكفي لاستنتاج زمن الحياة بنفسه في الحالات البسيطة. إليك متى يمكنك عدم التحديد:

\n\n
    \n
  • مرجع إدخال واحد - زمن حياة الإخراج يساوي زمن حياة

المدونات

توصيات الكتب