«استخدام القيمة المنقولة» في Rust: كيفية الإصلاح وعدم فقدان العقل

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

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

ابدأ الدورة

«استخدام القيمة المنقولة» في Rust: لماذا يحدث وكيف تتعايش معه

\n\n

إذا كنت قد بدأت للتو في تعلم Rust، فمن المحتمل أنك واجهت بالفعل الرسالة الغامضة: error[E0382]: use of moved value. المترجم يوبخ، وأنت تحك رأسك، والكود الذي هو منطقيًا صحيح تمامًا يرفض التجميع. هل هذا مألوف؟ مرحبًا بك في عالم الملكية والاقتراض — المفاهيم الأساسية في Rust التي تجعلها آمنة، ولكنها أحيانًا تحير حتى المطورين ذوي الخبرة.

\n\n

هذه المقالة ليست وثائق جافة، بل تحليل حي للمشكلة. سننظر في سبب ظهور خطأ use of moved value، من خلال أمثلة كود حقيقية، والأهم من ذلك، ماذا نفعل حيال ذلك. ستتعلم كيف تفكر مثل borrow checker وستتوقف عن محاربته. هيا بنا!

\n\n

ما هو «النقل» في Rust ولماذا المترجم صارم جدًا؟

\n\n

في Rust، لكل قيمة مالك واحد بالضبط. عندما تعين قيمة لمتغير آخر أو تمررها إلى دالة، فإن الملكية تُنقل. بعد النقل، يصبح المتغير الأصلي غير صالح — يمنعك المترجم من الوصول إليه. هذا هو جذر خطأ use of moved value.

\n\n

انظر إلى أبسط مثال:

\n\n
let s1 = String::from("مرحبًا");\nlet s2 = s1; // تنتقل الملكية من s1 إلى s2\nprintln!("{}", s1); // خطأ: use of moved value: `s1`
\n\n

لماذا؟ String ليست نسخة بسيطة في المكدس. إنها تخزن البيانات في الكومة. إذا قام Rust بنسخ المؤشر فقط، فعند تحرير الذاكرة، سيحاول كلا المتغيرين حذفها — double free. بدلاً من ذلك، يقوم Rust «بنقل» الملكية: الآن s2 مسؤول عن الذاكرة، و s1 لم يعد موجودًا من وجهة نظر المترجم. هذه هي الفكرة الأساسية لـ دلالات النقل في Rust.

\n\n

السيناريوهات النموذجية المؤدية إلى الخطأ

\n\n

يظهر خطأ use of moved value غالبًا في ثلاث حالات:

\n\n
    \n
  • تمرير القيمة إلى دالة
  • \n
  • تعيين القيمة لمتغير آخر
  • \n
  • استخدام القيمة في حلقة أو مكرر
  • \n
\n\n

دعنا ننظر في كل حالة بالتفصيل.

\n\n

1. التمرير إلى دالة دون إرجاع

\n\n
fn print_string(s: String) {\n    println!("{}", s);\n}\n\nfn main() {\n    let my_string = String::from("Rust");\n    print_string(my_string); // تم نقل الملكية\n    println!("{}", my_string); // خطأ! use of moved value\n}
\n\n

بعد استدعاء print_string، انتقلت ملكية my_string إلى الدالة. عندما تنتهي الدالة، يتم حذف s، وتصبح my_string غير صالحة. يحميك المترجم من استخدام كائن ميت.

\n\n

2. التعيين دون نسخ

\n\n

لقد رأينا هذا المثال مع String. لكن المهم: ليست كل الأنواع تتصرف هكذا. الأنواع البسيطة مثل i32 و bool و f64 تطبق الخاصية Copy. لا يتم نقلها، بل يتم نسخها تلقائيًا.

\n\n
let x = 5;\nlet y = x; // نسخة، وليس نقل\nprintln!("{}", x); // يعمل!
\n\n

الأنواع التي تخزن البيانات في الكومة (String، Vec، Box) لا تطبق Copy — فقط Clone. لذلك يتم نقلها.

\n\n

3. التكرار على مجموعة

\n\n

مصدر آخر شائع للأخطاء هو حلقات for:

\n\n
let v = vec![1, 2, 3];\nfor x in v { // يتم نقل v إلى المكرر\n    println!("{}", x);\n}\nprintln!("{:?}", v); // خطأ! use of moved value
\n\n

عندما تكتب for x in v، فأنت في الواقع تستدعي into_iter()، الذي يستهلك المتجه. بعد الحلقة، لم يعد v موجودًا.

\n\n

كيفية الإصلاح: 4 طرق مجربة

\n\n

الآن

المدونات

توصيات الكتب