Clone و Copy
جزء من قسم Object Oriented Programming في رحلة Rust على Coddy — الدرس 37 من 61.
في لغة Rust، عندما تقوم بتعيين قيمة لمتغير آخر، تنتقل الملكية عادةً—ويصبح المتغير الأصلي غير صالح. ومع ذلك، هناك سمتان قياسيتان تغيران هذا السلوك: Clone و Copy.
تُمكّن سمة Copy من **النسخ البتّي الضمني**. عندما يطبق نوع ما Copy، فإن تعيينه لمتغير آخر ينشئ نسخة تلقائية بدلاً من نقل الملكية:
#[derive(Copy, Clone)]
struct Point {
x: i32,
y: i32,
}
let p1 = Point { x: 10, y: 20 };
let p2 = p1; // يتم نسخ p1، وليس نقله
println!("{}, {}", p1.x, p2.x); // كلاهما صالح!
توفر سمة Clone نسخاً عميقاً صريحاً من خلال طريقة .clone(). وهي مطلوبة كلما قمت باشتقاق Copy، ولكن يمكن استخدامها أيضاً بمفردها للأنواع التي تحتاج إلى نسخ صريح:
let p3 = p1.clone(); // نسخ صريح
هناك قيد مهم: يمكن اشتقاق Copy فقط للأنواع التي تنفذ فيها جميع الحقول أيضاً Copy. الأنواع البسيطة مثل الأعداد الصحيحة (integers) والأعداد العشرية (floats) هي Copy، ولكن String ليست كذلك—فهي تدير ذاكرة الكومة (heap memory). إذا كانت الـ struct الخاصة بك تحتوي على String، فيمكنك فقط اشتقاق Clone وليس Copy.
| السمة | السلوك | الاستخدام |
|---|---|---|
Copy | ضمني، تلقائي | بيانات بسيطة مخزنة في المكدس (stack) فقط |
Clone | صريح عبر .clone() | أي بيانات قابلة للتكرار |
بالنسبة للهياكل (structs) البسيطة ذات الحقول الأولية، يتيح لك اشتقاق كلا السمتين (traits) تعيين القيم بحرية دون القلق بشأن الملكية (ownership).
التحدي
سهللنقم ببناء نظام إحداثيات يوضح الفرق بين سمات Copy و Clone! ستقوم بإنشاء هيكلين (structs) - أحدهما يمكن نسخه ضمنيًا والآخر يتطلب استنساخًا صريحًا - لترى كيف تتعامل لغة Rust مع التكرار بشكل مختلف بناءً على السمات التي تشتقها.
ستقوم بتنظيم الكود الخاص بك عبر ملفين:
coordinates.rs: قم بتعريف هيكلين عامين يمثلان أنواعًا مختلفة من الإحداثيات:GridPoint— مع حقول عامةxوy(كلاهماi32). نظرًا لأنه يحتوي فقط على أنواع أولية، قم باشتقاق كل منCopyوCloneبحيث يمكن نسخه ضمنيًا عند التعيين.NamedLocation— مع حقول عامةname(String) وx/y(i32). نظرًا لأنه يحتوي علىString، يمكنك فقط اشتقاقCloneوليسCopy. سيتطلب هذا الهيكل استدعاءات.clone()صريحة للتكرار.
main.rs: قم باستيراد وحدة الإحداثيات الخاصة بك ووضح كيف يتصرف كل هيكل بشكل مختلف عند تكراره. قم بإنشاء مثيلات باستخدام المدخلات المقدمة، ثم أظهر ما يلي:- يمكن تعيين
GridPointلمتغير آخر ويظل كلاهما صالحًا (نسخ ضمني) - يجب أن يستخدم
NamedLocationالتابع.clone()لإنشاء نسخة مكررة مع الحفاظ على صلاحية الأصل
- يمكن تعيين
يجب أن يظهر مخرجك كلاً من القيم الأصلية والمنسوخة/المستنسخة لإثبات أن كلا المتغيرين صالحان بعد التكرار:
Original point: ({x}, {y})
Copied point: ({x}, {y})
Original location: {name} at ({x}, {y})
Cloned location: {name} at ({x}, {y})على سبيل المثال، مع المدخلات 5 و 10 و Home:
Original point: (5, 10)
Copied point: (5, 10)
Original location: Home at (5, 10)
Cloned location: Home at (5, 10)ستتلقى ثلاثة مدخلات: إحداثي x (يتم تحليله كـ i32)، وإحداثي y (يتم تحليله كـ i32)، واسم الموقع.
ورقة مرجعية
تسمح سمة Copy بـ النسخ الضمني بتلو الآخر (bitwise). عندما يطبق نوع ما سمة Copy، فإن تعيينه لمتغير آخر ينشئ نسخة تلقائية بدلاً من نقل الملكية:
#[derive(Copy, Clone)]
struct Point {
x: i32,
y: i32,
}
let p1 = Point { x: 10, y: 20 };
let p2 = p1; // يتم نسخ p1، وليس نقله
println!("{}, {}", p1.x, p2.x); // كلاهما صالح!
توفر سمة Clone نسخاً عميقاً صريحاً من خلال التابع .clone():
let p3 = p1.clone(); // نسخ صريح
يمكن اشتقاق Copy فقط للأنواع التي تطبق فيها جميع الحقول سمة Copy أيضاً. الأنواع البسيطة مثل الأعداد الصحيحة والأعداد العشرية هي Copy، لكن String ليست كذلك. إذا كانت البنية (struct) الخاصة بك تحتوي على String، فيمكنك فقط اشتقاق Clone، وليس Copy.
| السمة | السلوك | الاستخدام |
|---|---|---|
Copy | ضمني، تلقائي | بيانات بسيطة في المكدس (stack) فقط |
Clone | صريح عبر .clone() | أي بيانات قابلة للتكرار |
عند اشتقاق Copy، يجب عليك أيضاً اشتقاق Clone.
جرّب بنفسك
mod coordinates;
use coordinates::{GridPoint, NamedLocation};
fn main() {
// قراءة المدخلات
let mut input1 = String::new();
std::io::stdin().read_line(&mut input1).expect("Failed to read line");
let x: i32 = input1.trim().parse().expect("Invalid number");
let mut input2 = String::new();
std::io::stdin().read_line(&mut input2).expect("Failed to read line");
let y: i32 = input2.trim().parse().expect("Invalid number");
let mut input3 = String::new();
std::io::stdin().read_line(&mut input3).expect("Failed to read line");
let name = input3.trim().to_string();
// TODO: إنشاء مثيل من GridPoint باستخدام x و y
// TODO: توضيح النسخ الضمني من خلال التعيين لمتغير آخر
// (تنفذ GridPoint سمة Copy، لذا فإن هذا ينشئ نسخة تلقائياً)
// TODO: طباعة النقطة الأصلية والمنسوخة
// التنسيق: "Original point: ({x}, {y})"
// التنسيق: "Copied point: ({x}, {y})"
// TODO: إنشاء مثيل من NamedLocation باستخدام name و x و y
// TODO: توضيح الاستنساخ الصريح (تنفذ NamedLocation سمة Clone فقط، وليس Copy)
// استخدم .clone() لإنشاء نسخة مكررة
// TODO: طباعة الموقع الأصلي والمستنسخ
// التنسيق: "Original location: {name} at ({x}, {y})"
// التنسيق: "Cloned location: {name} at ({x}, {y})"
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1الأساليب والسلوك
مقدمة في كتل التنفيذمعامل Selfالأساليب القابلة للتعديلالدوال المرتبطةكتل تنفيذ متعددةتسلسل الأساليبمراجعة - إجراءات المستطيل4مشروع: حيوان أليف افتراضي
تعريف الحيوان الأليفإطعام الحيوان الأليف2التغليف والوحدات البرمجية
أساسيات الوحدات البرمجيةالكلمة المفتاحية Publicالحقول الخاصةدوال Gettersدوال Settersمراجعة - الخزانة الآمنة5الأنواع العامة (Generics)
الـ Structs العامةالـ Methods العامةأنواع عامة متعددةالـ Functions العامةمراجعة - Coordinate Point3التعدادات المتقدمة (Advanced Enums)
التعدادات مع البياناتالأساليب (Methods) في التعداداتمطابقة حالات البياناتمراجعة التعداد Optionملخص - التعداد Shape6تعريف الـ Traits
ما هي الـ Trait؟تنفيذ الـ Traitsالتنفيذات الافتراضيةتجاوز التنفيذات الافتراضيةالـ Traits مع المعاملاتملخص - مشغل الوسائط