أنت هابط على كوكب لغته من عائلة 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
letيربط اسمًا بقيمة. النوع يُستنتج (كما اعتدت في TS: inference أولًا، التصريح عند الحاجة):let x: i64 = 5;.mutإعلانٌ مرئيّ في موضع التعريف وفي كل استعارة لاحقًا (سترى&mutفي الإقليم ٢). التعديل في Rust حدثٌ يُعلَن، لا وضعٌ افتراضي.
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:
- بناء debug: panic فوري (توقف البرنامج بصراخ).
- بناء release: wrap (التفاف كما في التمثيل الثنائي).
لماذا ليس سلوكًا واحدًا؟ لأن الفحص له ثمن في كل عملية جمع، وRust لا تدفع أثمانًا وقت التشغيل إلا بإذنك — لكنها أيضًا ترفض ترك الـ UB يمر صامتًا أثناء التطوير. المقايضة معلنة، والأدوات الصريحة موجودة حين يهمّك الأمر: wrapping_add, checked_add, saturating_add. (قارن بصمت C.)
الدرس ٢: الانعكاس الثاني — كل شيء تعبير
اللغز المستفز
هذا الكود لا يُترجَم. قبل أن تقرأ التفسير، شغّله واقرأ الرسالة كاملة — ثم أصلحه بحذف محرف واحد:
fn double(x: i32) -> i32 {
x * 2;
}
الرسالة ستقول شيئًا عن () ونوع متوقَّع i32. من أين جاء هذا الـ ()؟ ما الذي يفعله المحرف ; فعليًا في هذه اللغة؟
الآلية
في C، الفاصلة المنقوطة تُنهي جملة (statement). في Rust المعادلة مقلوبة:
- التعبير (expression) يُنتج قيمة:
5،x * 2،f(x)، وأيضًا — وهنا الانقلاب — الكتلة{}، وif، وmatch، وloop. ;يحوّل تعبيرًا إلى جملة، ويبتلع قيمته ويستبدلها بـ()— النوع الفارغ unit، وهو نوع حقيقي له قيمة واحدة هي نفسها(). (ليسvoidبمعنى "لا شيء" — بل "قيمة لا معلومة فيها".voidفي C ثقب في نظام الأنواع؛()عضو كامل فيه.)- قيمة الكتلة = قيمة آخر تعبير فيها بلا
;:
let y = {
let a = 3;
a * a // بلا ; — هذه قيمة الكتلة
}; // y == 9
let grade = if score >= 90 { "A" } else { "B" }; // if تعبير — لهذا لا يوجد ternary
- الدالة ترجع قيمة تعبيرها الأخير.
returnموجودة للخروج المبكر فقط؛ استعمالها في ذيل الدالة يفضح قادمًا من C. loopحلقة أبدية صريحة (بدلwhile(1)الملتفّة)، وهي أيضًا تعبير:breakيستطيع أن يحمل قيمة الحلقة:
let first_power = {
let mut n: u64 = 1;
loop {
if n > 1000 { break n; } // الحلقة كلها تُقيَّم إلى n
n *= 2;
}
};
whileوforكما تتوقع شكلًا — لكنforفي Rust لا تملك نسخة العدّاد الثلاثيةfor(;;)نهائيًا. فقطfor x in 0..10(نصف مفتوح) أو0..=10(مغلق). لماذا حُذفت أشهر جملة في تاريخ C؟ الجواب الكامل في الإقليم ٦ — وستقتنع أن الحذف كان رحمة.
- الدوال:
fn name(param: Type) -> RetType { body }. الأنواع في التواقيع إلزامية دائمًا — لا inference للتواقيع عمدًا، حتى يبقى العقد العام مقروءًا بلا تشغيل خوارزمية الاستنتاج في رأسك (تذكّر من منهج TS كيف كانت تواقيع الدوال هي العقود).
println!("x = {x}");— علامة التعجب تعني macro لا دالة. لماذا تحتاج الطباعة macro أصلًا؟ (تلميح للفضول فقط: عدد معاملات متغيّر + فحص صيغة التنسيق وقت الترجمة — لا شيء منهما تستطيعه دالة عادية في لغة بلا varargs آمنة.) الماكرو نفسه قارة مؤجلة خارج هذا المنهج.
الألغاز
تذكير: لا حلول في المنهج. اللغز انتهى حين يعمل كودك وأنت قادر على شرح كل سطر منه لجدار.
لغز ٠٠-أ: المتنبئ. اكتب على ورقة — قبل أي تشغيل — مخرجات هذا البرنامج في 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" بل تمهيد بنيوي:
- الثبات افتراضًا ليس ذوقًا — إنه المعلومة التي سيبني عليها قانونُ الإقليم ٢ نظامَه كله. لغة لا تعرف من يعدّل ماذا لا تستطيع أن تثبت شيئًا.
- كل شيء تعبير ليس أناقة — إنه ما سيجعل
matchفي الإقليم ٣ أداة تركيب لا جملة تحكم، وما يجعل الكود الاصطلاحي في Rust يُبنى تركيبًا (composition) لا تسلسلَ أوامر.
لاحظتَ أن كل أمثلة هذا الإقليم تجنّبت النصوص إلا كـ literals؟ لم نكتب String واحدة. النوع الذي تعتبره أبسط أنواع أي لغة هو هنا بوابة أعمق قانون في اللغة كلها — وهو موعدك مع الإقليم القادم.