الدرس2 / 2

الملكية والنقل

قراءة

اللي هتفهمه

  • قواعد الملكية التلاتة، وإيه اللي بيحصل في الذاكرة لما القيمة تتنقل.
  • ليه الكومبايلر بيرفض استخدام قيمة بعد نقلها، وإيه الكارثة اللي بيمنعها.
  • إمتى الإسناد بينسخ فعلاً بدل ما ينقل، وإيه القاعدة اللي بتفرّق.

الشرح

الدرس اللي فات انتهى عند سؤال: مين بيحرر البايتات اللي على الهيب؟ الإجابة في Rust اسمها الملكية، وهي تلات قواعد بس:

  1. كل قيمة في Rust ليها متغير واحد اسمه المالك.
  2. في أي لحظة، مالك واحد بس.
  3. لما المالك يخرج من نطاقه، القيمة بتتحرر.

القواعد دي سهلة تقراها وصعبة تصدّق نتايجها، لأن نتيجتها الأولى بتكسر حاجة إنت متعوّد عليها في كل لغة تانية. بص على السطرين دول:

let s1 = String::from("hello");
let s2 = s1;

في Java أو Python، دلوقتي عندك اسمين بيشاوروا على نفس الكائن. في C++ ممكن تبقى عملت نسخة كاملة. في Rust حصل حاجة تالتة: s1 مبقاش موجود.

مش اتمسح، ومش اتنقل مكانه. القيمة اتنقلت ملكيتها لـ s2، والكومبايلر شطب s1 من قائمة المتغيرات الصالحة للاستخدام من السطر ده ورايح.

طيب ليه؟ عشان القاعدة رقم 3. لو الاتنين فضلوا صالحين، لما الدالة تخلص كل واحد فيهم هيحرر نفس البايتات — تحرير مزدوج. النقل مش تعقيد زيادة، هو النتيجة المباشرة لقاعدة “مالك واحد”.

اتفرج على ده بيحصل خطوة بخطوة:

المرئي

إيه اللي بيحصل بالظبط لما تكتب let s2 = s1

fn main() {    let s1 = String::from("hello");    let s2 = s1;    println!("{}", s1);    println!("{}", s2);}

البرنامج بدأ. الـ frame بتاع main اتفتح وهو فاضي. لسه مفيش أي حاجة على الهيب.

الخطوة 1 / 7

استخدم الأسهم في لوحة المفاتيح، أو اسحب بإصبعك على الشاشة.

الكود

الكود ده مش بيترجم — وده المقصود:

fn main() {
    let s1 = String::from("hello");
    let s2 = s1;

    println!("{}", s1);
    println!("{}", s2);
}

الكومبايلر بيرد بالرسالة دي. اقرا الشرح تحتها الأول لو الصندوق مقطوع عندك على الموبايل — الشرح بيقول نفس الكلام كامل:

error[E0382]: borrow of moved value: `s1`
 --> src/main.rs:5:20
  |
2 |     let s1 = String::from("hello");
  |         -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
3 |     let s2 = s1;
  |              -- value moved here
4 |
5 |     println!("{}", s1);
  |                    ^^ value borrowed here after move
  |
help: consider cloning the value if the performance cost is acceptable
  |
3 |     let s2 = s1.clone();
  |                ++++++++

rustc 1.97.1 (8bab26f4f 2026-07-14)

الرسالة دي بتقول أربع حاجات بالترتيب:

  • العنوان: borrow of moved value: s1 — إنت بتستخدم s1 بعد ما اتنقلت.
  • السبب، وده أهم سطر فيها: النقل حصل لأن نوع s1 هو String، والنوع ده مش بيطبّق الـ Copy trait. يعني الإسناد في اللغة دي بينقل الملكية، مش بينسخ. لو كان نوعه بيطبّق Copy كان الكلام اختلف تماماً — وهنشوف ده بعد شوية.
  • مكان النقل: سطر 3، let s2 = s1;.
  • مكان الاستخدام الغلط: سطر 5، والسهمين تحت s1 بالظبط.

وبعدين بيقترح عليك حل: s1.clone().

الحل الأول: انسخ فعلاً

لو إنت فعلاً عايز نسختين، اطلب كده صراحة:

fn main() {
    let s1 = String::from("hello");
    let s2 = s1.clone();

    println!("s1 = {s1}, s2 = {s2}");
}
افتح في Playground

ده بيشتغل وبيطبع s1 = hello, s2 = hello. clone بتحجز مساحة جديدة على الهيب وبتنسخ الحروف فيها، فبقى عندك مالكين لبايتات مختلفة. التكلفة حقيقية، بس إنت اللي طلبتها وشايفها مكتوبة في الكود.

وليه الأرقام مش بتعمل كده؟

الكود ده بيترجم عادي:

fn main() {
    let x = 5;
    let y = x;

    println!("x = {x}, y = {y}");
}
افتح في Playground

مفيش نقل هنا، فيه نسخ. السبب إن i32 كله قاعد على الستاك — حجمه معروف، مفيش عنوان هيب، ومفيش حاجة محتاجة تتحرر. نسخ 4 بايت رخيص وآمن، فالنسخ هو التصرف الافتراضي. الأنواع دي بتطبّق الـ Copy trait، ودي بالظبط الجملة اللي رسالة الخطأ فوق قالت إن String مش بتطبّقها.

القاعدة العملية: أي نوع بيملك حاجة على الهيب مش بيطبّق Copy. الأنواع البسيطة كلها بتطبّقه — الأرقام، bool، char، والـ tuples اللي كل عناصرها بسيطة.

ليه بيشتغل كده

الجزء اللي بيرفض كودك اسمه الـ borrow checker، وهو تحليل ساكن — بيشتغل على نص الكود وهو بيتترجم، مش وهو شغال.

هو بيتتبع لكل قيمة: مين مالكها دلوقتي، وفين آخر مكان المالك ده اتستخدم فيه. من المعلومتين دول بيحط أمر التحرير في المكان الصح وقت الترجمة. لما تكتب let s2 = s1; هو بيعلّم s1 كـ منقولة، وأي استخدام بعد كده بيبقى خطأ ترجمة.

خد بالك من نتيجتين مهمين:

الأولى: مفيش أي تكلفة وقت التشغيل. البرنامج المترجم مفيهوش عدّاد مراجع ولا GC ولا فحص. النقل نفسه — اللي شفته في المرئي — هو نسخ 24 بايت من مكان لمكان على الستاك، وغالباً المُحسِّن بيشيله خالص.

التانية: الكومبايلر بيرفض حاجات كانت هتشتغل صح. هو مش بيقولك “غلطت”، هو بيقولك “مقدرتش أتأكد”. لو كتبت كود إنت متأكد إنه سليم بس هو مش قادر يثبته، هيرفضه برضه. الحل مش إنك تتحايل عليه — الحل إنك تتعلم تكتب بالشكل اللي بيخليه يقدر يثبت، وده اللي الدروس الجاية عنه.

تمرين

الكود ده مش بيترجم. من غير ما تشغّله، جاوب على تلات أسئلة:

fn takes_ownership(some_string: String) {
    println!("{some_string}");
}

fn main() {
    let s = String::from("hello");
    takes_ownership(s);

    println!("{}", s);
}
  1. في أنهي سطر بالظبط اتنقلت الملكية؟
  2. مين المالك بعد السطر ده؟
  3. لو غيّرت نوع s لـ i32some_string لـ i32)، الكود هيترجم ولا لأ، وليه؟
الحل

1. في سطر 7، takes_ownership(s);. تمرير قيمة لدالة بينقل الملكية بالظبط زي الإسناد — مفيش فرق بين let x = s; وf(s) من ناحية الملكية.

2. الباراميتر some_string جوه الدالة بقى المالك. ولما الدالة خلصت، some_string خرج من نطاقه، والبايتات اتحررت هناك — قبل ما نرجع لـ main أصلاً.

3. هيترجم. i32 بيطبّق Copy، فتمريره للدالة بينسخ 4 بايت والأصل بيفضل صالح. مفيش حاجة على الهيب، فمفيش سؤال أصلاً عن مين يحررها.

ودي رسالة الكومبايلر على النسخة الأصلية — لاحظ إنه هنا بيقترح حل تاني غير clone، وهو تغيير نوع الباراميتر نفسه عشان ياخد استعارة بدل ملكية. دي هي فكرة الدرس الجاي:

error[E0382]: borrow of moved value: `s`
 --> src/main.rs:9:20
  |
6 |     let s = String::from("hello");
  |         - move occurs because `s` has type `String`, which does not implement the `Copy` trait
7 |     takes_ownership(s);
  |                     - value moved here
8 |
9 |     println!("{}", s);
  |                    ^ value borrowed here after move
  |
note: consider changing this parameter type in function `takes_ownership` to borrow instead if owning the value isn't necessary
 --> src/main.rs:1:33
  |
1 | fn takes_ownership(some_string: String) {
  |    ---------------              ^^^^^^ this parameter takes ownership of the value
  |    |
  |    in this function
help: consider cloning the value if the performance cost is acceptable
  |
7 |     takes_ownership(s.clone());
  |                      ++++++++

rustc 1.97.1 (8bab26f4f 2026-07-14)

مشاكل شائعة

النقاش

لو عندك سؤال أو لقيت غلطة في الصفحة دي، اكتبها فيالنقاشات على GitHub. كل صفحة ليها موضوعها الخاص.