مقدمة: لماذا Copy و Clone ليسا نفس الشيء؟
\n\nكل من يبدأ في تعلم Rust، عاجلاً أم آجلاً، سيواجه traitين غامضين: Copy و Clone. للوهلة الأولى، يفعلان نفس الشيء – يسمحان لك بالحصول على نسخة مكررة من القيمة. لكن الشيطان، كما هو الحال دائمًا، يكمن في التفاصيل.
تخيل أنك تكتب برنامجًا وتمرر قيمة إلى دالة. في C++ سيكون هذا نسخًا (أو نقلًا، إذا كنت محظوظًا). في Python – مجرد مرجع آخر إلى الكائن. أما في Rust... فإن Rust تجبرك على أن تقرر بوضوح: "أريد أن يتم نسخ هذه القيمة" أو "أريد استنساخها مع إدراك التكلفة".
\n\nهنا بالضبط يمر الحد الفاصل بين Copy و Clone. الأول هو "نسخ خفيف bit-by-bit". الثاني هو "استنساخ واعٍ، وربما مكلف".
دعنا نفهم بالأمثلة متى وماذا نستخدم، بحيث يكون كود Rust الخاص بك ليس صحيحًا فحسب، بل فعالًا أيضًا.
\n\nباختصار عن الأساسي: Copy مقابل Clone
\n\nما هو Clone؟
\n\nClone هو trait يمنحك طريقة .clone(). باستدعائها، تقول بوضوح للمترجم: "أعلم أن هذا قد يكون مكلفًا، لكنني أحتاج إلى نسخة مستقلة".
خصائص Clone:
- \n
- يمكن تنفيذه لأي نوع \n
- يتطلب استدعاءً صريحًا لـ
.clone()\n - يمكنه إجراء نسخ عميق (مثلاً لـ
VecأوString) \n - لا يغير دلالات الملكية – بعد الاستنساخ، لديك قيمتان مستقلتان \n
ما هو Copy؟
\n\nCopy هو trait "علامة" (بدون طرق). إذا كان النوع يطبق Copy، يمكن للمترجم ببساطة نسخ بتاته عند التعيين أو التمرير إلى دالة. هذا لا يتطلب استدعاء .clone().
خصائص Copy:
- \n
- يعمل فقط مع الأنواع البسيطة التي يمكن نسخها بأمان bitwise \n
- لا يتطلب استدعاءً صريحًا – يحدث النسخ تلقائيًا \n
- يجب أن يظل النوع مطبقًا لـ
Clone(هذا هو supertrait) \n - تبقى القيمة الأصلية صالحة بعد النسخ \n
أمثلة عملية: كيف يعمل هذا في الكود
\n\nمثال 1: الأنواع الأساسية (Copy)
\n\nالأعداد الصحيحة، الأعداد العشرية، القيم المنطقية – جميعها تطبق Copy:
fn main() {\n let x = 42;\n let y = x; // x يُنسخ إلى y، x لا يزال متاحًا\n \n println!("x = {}, y = {}", x, y); // يعمل!\n}\n\nهنا x هو i32، الذي يطبق Copy. لذلك عند التعيين y = x يحدث النسخ، ويبقى x صالحًا.
مثال 2: String (فقط Clone)
\n\nأما String فهو نوع معقد، يخزن البيانات في الكومة (heap). يطبق Clone، لكن ليس Copy:
fn main() {\n let s1 = String::from("مرحبًا، Rust!");\n let s2 = s1; // s1 يُنقل إلى s2\n \n // println!("{}", s1); // خطأ! s1 لم يعد يملك البيانات\n \n let s3 = s2.clone(); // هكذا يمكن – استنساخ صريح\n println!("s2 = {}, s3 = {}", s2, s3); // يعمل!\n}\n\nلماذا لا يمكن لـ String أن يكون Copy؟ لأن النسخ bitwise لـ String سيؤدي إلى مؤشرين لنفس الذاكرة في الكومة. عند drop كلتا القيمتين – تحرير مزدوج للذاكرة. أما Clone فينشئ نسخة مستقلة كاملة.
مثال 3: نوع مخصص مع Copy
\n\nيمكنك جعل نوعك الخاص Copy، إذا كان يتكون فقط من أنواع Copy:
#[derive(Debug, Copy, Clone)]\nstruct Point {\n x: f64,\n y: f64,\n}\n\nfn main() {\n let p1 = Point { x: 1.0, y: 2.0 };\n let p2 = p1; // يُنسخ، p1 يبقى\n \n println!