دستور الملكية
Rust

أنت هابط على كوكب لغته من عائلة C — أقواس معقوفة، أنواع صريحة، ترجمة إلى كود آلة حقيقي — لكن فيزياءه معكوسة في نقطتين ستشعر بهما في أول عشرة أسطر: كل شيء ثابت ما لم تعلن غير ذلك، وكل شيء تقريبًا تعبير (expression) له قيمة. هذا الإقليم يعطيك اللغة "الميّتة" — الجزء الذي لا يحتاج قوانين الملكية — عبر الفرق التصميمي عن C وTS، لا عبر سرد المزايا.


الدرس ١: الانعكاس الأول — الثبات هو الافتراض

اللغز الافتتاحي

قبل أي syntax. في C، كل متغير قابل للتعديل افتراضيًا وconst خيار تجميلي نادرًا ما يستعمله أحد. اقلب السؤال: لو صمّمتَ أنت لغة، أيهما تجعل الافتراض وأيهما يدفع صاحبه ثمن كتابته؟ فكّر بمعيار واحد: أيّ المعلومتين أثمن لقارئ الكود — "هذا قد يتغيّر" أم "هذا لن يتغيّر"؟ وأيهما أثمن للمترجم؟ اكتب إجابتك قبل المتابعة، وسنعود لها في الإقليم ٢ حيث يتضح أن هذا القرار لم يكن ذوقيًا إطلاقًا.

الآلية

let x = 5;        // ثابت. ليس "متغيّرًا" أصلًا — إنه ربط (binding)
let mut y = 5;    // الآن فقط يجوز: y += 1
y += 1;
// x += 1;        // error[E0384]: cannot assign twice to immutable variable

Shadowing — مسموح ومُستعمَل بكثرة، وليس غلطة كما في C:

let input = "42";
let input: i64 = input.trim().parse().unwrap(); // اسم واحد، ربط جديد، نوع جديد

لماذا تسمح لغة متزمّتة بهذا؟ لأن الـ shadowing ليس تعديلًا — الربط القديم مات والجديد وُلد. لا أحد يملك مرجعًا لقيمة تغيّرت تحته. قارنه بـ mut: الاسم نفسه، الذاكرة نفسها، القيمة تتبدل. الفرق سيصبح جوهريًا في الإقليم ٢.

الأنواع الأولية — فروقات C فقط

أنت تعرف الأعداد ذات العرض الصريح من C (stdint.h). الجديد فقط:

النوعالملاحظة التصميمية
i8..i128, u8..u128كما توقعت. لا int غامض الحجم — الغموض كان bug في C أصلًا.
usize, isizeنظير size_t/ptrdiff_t: حجم المؤشر. كل فهرسة تتم بـ usize — والمترجم لن يحوّل لك ضمنيًا. لا يوجد أي تحويل عددي ضمني في Rust، إطلاقًا: as أو From/into صريحة دائمًا. تذكّر كوارث الـ implicit promotion في C.
f32, f64كما هي. (في الإقليم ٤ ستكتشف سرًّا قذرًا عن مقارنتها.)
boolحقيقي، ليس int متنكّرًا. لا truthiness إطلاقًا: if x {} حيث x: i32 خطأ ترجمة. تذكّر منهج TS وقرار strict — Rust لا تعطيك خيار التساهل أصلًا.
char٤ بايتات، وليس بايتًا واحدًا. Unicode scalar value كامل. لماذا؟ هذا طرف خيط سيُسحب بالكامل في الإقليم ٦ — علّقه في ذهنك.

الـ overflow: في C هو undefined behavior للـ signed. في Rust:

لماذا ليس سلوكًا واحدًا؟ لأن الفحص له ثمن في كل عملية جمع، وRust لا تدفع أثمانًا وقت التشغيل إلا بإذنك — لكنها أيضًا ترفض ترك الـ UB يمر صامتًا أثناء التطوير. المقايضة معلنة، والأدوات الصريحة موجودة حين يهمّك الأمر: wrapping_add, checked_add, saturating_add. (قارن بصمت C.)


الدرس ٢: الانعكاس الثاني — كل شيء تعبير

اللغز المستفز

هذا الكود لا يُترجَم. قبل أن تقرأ التفسير، شغّله واقرأ الرسالة كاملة — ثم أصلحه بحذف محرف واحد:

fn double(x: i32) -> i32 {
    x * 2;
}

الرسالة ستقول شيئًا عن () ونوع متوقَّع i32. من أين جاء هذا الـ ()؟ ما الذي يفعله المحرف ; فعليًا في هذه اللغة؟

الآلية

في C، الفاصلة المنقوطة تُنهي جملة (statement). في Rust المعادلة مقلوبة:

let y = {
    let a = 3;
    a * a          // بلا ; — هذه قيمة الكتلة
};                 // y == 9

let grade = if score >= 90 { "A" } else { "B" };  // if تعبير — لهذا لا يوجد ternary
let first_power = {
    let mut n: u64 = 1;
    loop {
        if n > 1000 { break n; }   // الحلقة كلها تُقيَّم إلى n
        n *= 2;
    }
};

الألغاز

تذكير: لا حلول في المنهج. اللغز انتهى حين يعمل كودك وأنت قادر على شرح كل سطر منه لجدار.

لغز ٠٠-أ: المتنبئ. اكتب على ورقة — قبل أي تشغيل — مخرجات هذا البرنامج في cargo run ثم في cargo run --release، أو سبب فشل ترجمته إن كان يفشل:

fn main() {
    let x: u8 = 250;
    let x = x.wrapping_add(10);
    let y = {
        let x = x as i32;
        x - 300;
    };
    println!("{x:?} {y:?}");
    let z = if x > 0 { x } else { 0u8 + 255 + 5 };
    println!("{z}");
}

ثم شغّله وفسّر كل فرق بين توقعك والواقع. (فيه ثلاثة أفخاخ على الأقل.)

لغز ٠٠-ب: الآلة الجامدة. اكتب برنامجًا يقرأ عددًا من سطر الأوامر (std::env::args) ويطبع تمثيله بالأساس ١٦ دون استعمال أي تنسيق جاهز للأساسات ({:x} ممنوعة) — ابنِ التحويل بنفسك. القيود: ممنوع mut نهائيًا في كامل البرنامج، وممنوع while/for. (نعم، هذا يجبرك على أسلوب كامل ربما لم تستعمله منذ زمن — هذا هو المقصود: أن تكتشف أن اللغة تعيش بلا mutation بطلاقة تامة.) ملاحظة: unwrap() مسموح مؤقتًا أينما ظهر Result — سنحاسبك عليه في الإقليم ٣.

لغز ٠٠-ج: جرّاح الفواصل. البرنامج التالي فيه ٥ أخطاء ترجمة، كلها من عائلة واحدة. أصلحه بإضافة أو حذف ; ومحارف {} فقط — يُمنع تغيير أي شيء آخر:

fn classify(n: i32) -> &'static str {
    let sign = if n < 0 { "neg"; } else { "pos" };
    let parity = {
        let r = n % 2;
        if r == 0 { "even" } else { "odd" };
    };
    let msg = loop {
        break "done";
    };
    if n == 0 { return "zero"; };
    sign
}

fn main() {
    let c = classify(-4)
    println!("{c}");
}

(لاحظ بعد الحل: كم خطأً منها كشفه لك نوع ()؟ هذا النوع "الفارغ" هو أفضل مخبر في اللغة.)


الخلاصة

عقدتان جديدتان في الشجرة، وكلاهما ليس "ميزة syntax" بل تمهيد بنيوي:

بذرة الغموض

لاحظتَ أن كل أمثلة هذا الإقليم تجنّبت النصوص إلا كـ literals؟ لم نكتب String واحدة. النوع الذي تعتبره أبسط أنواع أي لغة هو هنا بوابة أعمق قانون في اللغة كلها — وهو موعدك مع الإقليم القادم.