Copy و Clone في Rust: ما الفرق وماذا تختار؟

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

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

ابدأ الدورة

مقدمة: لماذا Copy و Clone ليسا نفس الشيء؟

\n\n

كل من يبدأ في تعلم Rust، عاجلاً أم آجلاً، سيواجه traitين غامضين: Copy و Clone. للوهلة الأولى، يفعلان نفس الشيء – يسمحان لك بالحصول على نسخة مكررة من القيمة. لكن الشيطان، كما هو الحال دائمًا، يكمن في التفاصيل.

\n\n

تخيل أنك تكتب برنامجًا وتمرر قيمة إلى دالة. في C++ سيكون هذا نسخًا (أو نقلًا، إذا كنت محظوظًا). في Python – مجرد مرجع آخر إلى الكائن. أما في Rust... فإن Rust تجبرك على أن تقرر بوضوح: "أريد أن يتم نسخ هذه القيمة" أو "أريد استنساخها مع إدراك التكلفة".

\n\n

هنا بالضبط يمر الحد الفاصل بين Copy و Clone. الأول هو "نسخ خفيف bit-by-bit". الثاني هو "استنساخ واعٍ، وربما مكلف".

\n\n

دعنا نفهم بالأمثلة متى وماذا نستخدم، بحيث يكون كود Rust الخاص بك ليس صحيحًا فحسب، بل فعالًا أيضًا.

\n\n

باختصار عن الأساسي: Copy مقابل Clone

\n\n

ما هو Clone؟

\n\n

Clone هو trait يمنحك طريقة .clone(). باستدعائها، تقول بوضوح للمترجم: "أعلم أن هذا قد يكون مكلفًا، لكنني أحتاج إلى نسخة مستقلة".

\n\n

خصائص Clone:

\n
    \n
  • يمكن تنفيذه لأي نوع
  • \n
  • يتطلب استدعاءً صريحًا لـ .clone()
  • \n
  • يمكنه إجراء نسخ عميق (مثلاً لـ Vec أو String)
  • \n
  • لا يغير دلالات الملكية – بعد الاستنساخ، لديك قيمتان مستقلتان
  • \n
\n\n

ما هو Copy؟

\n\n

Copy هو trait "علامة" (بدون طرق). إذا كان النوع يطبق Copy، يمكن للمترجم ببساطة نسخ بتاته عند التعيين أو التمرير إلى دالة. هذا لا يتطلب استدعاء .clone().

\n\n

خصائص Copy:

\n
    \n
  • يعمل فقط مع الأنواع البسيطة التي يمكن نسخها بأمان bitwise
  • \n
  • لا يتطلب استدعاءً صريحًا – يحدث النسخ تلقائيًا
  • \n
  • يجب أن يظل النوع مطبقًا لـ Clone (هذا هو supertrait)
  • \n
  • تبقى القيمة الأصلية صالحة بعد النسخ
  • \n
\n\n

أمثلة عملية: كيف يعمل هذا في الكود

\n\n

مثال 1: الأنواع الأساسية (Copy)

\n\n

الأعداد الصحيحة، الأعداد العشرية، القيم المنطقية – جميعها تطبق Copy:

\n\n
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 صالحًا.

\n\n

مثال 2: String (فقط Clone)

\n\n

أما String فهو نوع معقد، يخزن البيانات في الكومة (heap). يطبق Clone، لكن ليس Copy:

\n\n
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 فينشئ نسخة مستقلة كاملة.

\n\n

مثال 3: نوع مخصص مع Copy

\n\n

يمكنك جعل نوعك الخاص Copy، إذا كان يتكون فقط من أنواع Copy:

\n\n
#[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!                

المدونات

توصيات الكتب