Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

المتغيرات والقابلية للتغير (Variables and Mutability)

كما ذُكر في قسم “تخزين القيم باستخدام المتغيرات”، تكون المتغيرات (Variables) غير قابلة للتغير (Immutable) بشكل افتراضي. هذه واحدة من العديد من التوجيهات التي تقدمها لك لغة Rust لكتابة الكود (Code) الخاص بك بطريقة تستفيد من الأمان والتزامن (Concurrency) السهل الذي توفره Rust. ومع ذلك، لا يزال لديك الخيار لجعل Variables الخاصة بك قابلة للتغير (Mutable). دعنا نستكشف كيف ولماذا تشجعك Rust على تفضيل عدم القابلية للتغير (Immutability) ولماذا قد ترغب أحياناً في إلغاء هذا الاختيار.

عندما يكون Variable غير قابل للتغير، فبمجرد ربط قيمة باسم ما، لا يمكنك تغيير تلك القيمة. لتوضيح ذلك، قم بإنشاء مشروع جديد يسمى variables في دليل المشاريع الخاص بك باستخدام الأمر cargo new variables.

بعد ذلك، في دليل variables الجديد، افتح ملف src/main.rs واستبدل الكود الموجود فيه بالكود التالي، والذي لن يتم تحويله برمجياً (Compile) بعد:

اسم الملف: src/main.rs

fn main() {
    let x = 5;
    println!("The value of x is: {x}");
    x = 6;
    println!("The value of x is: {x}");
}

احفظ البرنامج وشغله باستخدام cargo run. يجب أن تتلقى رسالة خطأ تتعلق بخطأ في Immutability، كما هو موضح في هذا المخرج:

$ cargo run
   Compiling variables v0.1.0 (file:///projects/variables)
error[E0384]: cannot assign twice to immutable variable `x`
 --> src/main.rs:4:5
  |
2 |     let x = 5;
  |         - first assignment to `x`
3 |     println!("The value of x is: {x}");
4 |     x = 6;
  |     ^^^^^ cannot assign twice to immutable variable
  |
help: consider making this binding mutable
  |
2 |     let mut x = 5;
  |         +++

For more information about this error, try `rustc --explain E0384`.
error: could not compile `variables` (bin "variables") due to 1 previous error

يوضح هذا المثال كيف يساعدك المترجم (Compiler) في العثور على الأخطاء في برامجك. قد تكون أخطاء Compiler محبطة، لكنها في الحقيقة تعني فقط أن برنامجك لا يفعل ما تريده بأمان بعد؛ وهي لا تعني أنك لست مبرمجاً جيداً! حتى مبرمجي Rust المتمرسين لا يزالون يتلقون أخطاء Compiler.

لقد تلقيت رسالة الخطأ cannot assign twice to immutable variable `x` لأنك حاولت تعيين قيمة ثانية لـ Variable x غير القابل للتغير.

من المهم أن نحصل على أخطاء في وقت التحويل البرمجي (Compile-time Errors) عندما نحاول تغيير قيمة تم تحديدها على أنها Immutable، لأن هذا الموقف بحد ذاته يمكن أن يؤدي إلى أخطاء برمجية (Bugs). إذا كان جزء من Code الخاص بنا يعمل بناءً على افتراض أن القيمة لن تتغير أبداً وقام جزء آخر من Code بتغيير تلك القيمة، فمن المحتمل ألا يقوم الجزء الأول من Code بما صُمم للقيام به. قد يكون من الصعب تتبع سبب هذا النوع من Bug بعد وقوعه، خاصة عندما يغير الجزء الثاني من Code القيمة أحياناً فقط. يضمن Compiler الخاص بـ Rust أنه عندما تصرح بأن القيمة لن تتغير، فإنها لن تتغير حقاً، لذا لا يتعين عليك تتبع ذلك بنفسك. وبالتالي يصبح من الأسهل فهم منطق Code الخاص بك.

لكن القابلية للتغير (Mutability) يمكن أن تكون مفيدة جداً ويمكن أن تجعل كتابة Code أكثر ملاءمة. على الرغم من أن Variables تكون Immutable افتراضياً، يمكنك جعلها Mutable عن طريق إضافة الكلمة المفتاحية mut أمام اسم Variable كما فعلت في الفصل 2. إضافة mut تنقل أيضاً النية للقراء المستقبليين لـ Code من خلال الإشارة إلى أن أجزاء أخرى من Code ستغير قيمة Variable هذا.

على سبيل المثال، لنقم بتغيير src/main.rs إلى ما يلي:

اسم الملف: src/main.rs

fn main() {
    let mut x = 5;
    println!("The value of x is: {x}");
    x = 6;
    println!("The value of x is: {x}");
}

عندما نشغل البرنامج الآن، نحصل على هذا:

$ cargo run
   Compiling variables v0.1.0 (file:///projects/variables)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30s
     Running `target/debug/variables`
The value of x is: 5
The value of x is: 6

يُسمح لنا بتغيير القيمة المرتبطة بـ x من 5 إلى 6 عند استخدام mut. في النهاية، قرار استخدام Mutability من عدمه يعود إليك ويعتمد على ما تعتقد أنه الأكثر وضوحاً في ذلك الموقف المعين.

التصريح عن الثوابت (Declaring Constants)

مثل Variables غير القابلة للتغير، فإن الثوابت (Constants) هي قيم مرتبطة باسم ولا يُسمح بتغييرها، ولكن هناك بعض الاختلافات بين Constants و Variables.

أولاً، لا يُسمح لك باستخدام mut مع Constants. الثوابت ليست فقط Immutable افتراضياً - بل هي دائماً Immutable. تقوم بالتصريح عن Constants باستخدام الكلمة المفتاحية const بدلاً من الكلمة المفتاحية let ، و يجب تحديد نوع (Type) القيمة. سنغطي الأنواع وتوصيفات الأنواع (Type Annotations) في القسم التالي، “أنواع البيانات”، لذا لا تقلق بشأن التفاصيل الآن. فقط اعلم أنه يجب عليك دائماً توصيف Type.

يمكن التصريح عن Constants في أي نطاق (Scope)، بما في ذلك Scope العالمي، مما يجعلها مفيدة للقيم التي تحتاج أجزاء كثيرة من Code إلى معرفتها.

الاختلاف الأخير هو أنه لا يجوز تعيين Constants إلا لتعبير ثابت (Constant Expression)، وليس لنتيجة قيمة لا يمكن حسابها إلا في وقت التشغيل (Runtime).

إليك مثال على التصريح عن ثابت:

#![allow(unused)]
fn main() {
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
}

اسم الثابت هو THREE_HOURS_IN_SECONDS ، وقيمته محددة بنتيجة ضرب 60 (عدد الثواني في الدقيقة) في 60 (عدد الدقائق في الساعة) في 3 (عدد الساعات التي نريد عدها في هذا البرنامج). اصطلاح التسمية في Rust لـ Constants هو استخدام جميع الأحرف الكبيرة مع وجود شرطات سفلية بين الكلمات. يستطيع Compiler تقييم مجموعة محدودة من العمليات في وقت التحويل البرمجي، مما يتيح لنا اختيار كتابة هذه القيمة بطريقة يسهل فهمها والتحقق منها، بدلاً من تعيين هذا الثابت للقيمة 10,800. راجع قسم مرجع Rust حول تقييم الثوابت لمزيد من المعلومات حول العمليات التي يمكن استخدامها عند التصريح عن Constants.

تكون Constants صالحة طوال وقت تشغيل البرنامج، ضمن Scope الذي تم التصريح عنها فيه. تجعل هذه الخاصية Constants مفيدة للقيم في مجال تطبيقك التي قد تحتاج أجزاء متعددة من البرنامج إلى معرفتها، مثل الحد الأقصى لعدد النقاط التي يُسمح لأي لاعب في اللعبة بالحصول عليها، أو سرعة الضوء.

يعد تسمية القيم المكتوبة مباشرة (Hardcoded Values) المستخدمة في جميع أنحاء برنامجك كـ Constants أمراً مفيداً في نقل معنى تلك القيمة للمسؤولين عن صيانة Code في المستقبل. يساعد أيضاً وجود مكان واحد فقط في Code الخاص بك تحتاج إلى تغييره إذا احتاجت Hardcoded Value إلى التحديث في المستقبل.

الحجب (Shadowing)

كما رأيت في برنامج لعبة التخمين في الفصل 2، يمكنك التصريح عن Variable جديد بنفس اسم Variable سابق. يقول مبرمجو Rust إن Variable الأول قد تم حجبه (Shadowed) بواسطة الثاني، مما يعني أن Variable الثاني هو ما سيراه Compiler عند استخدام اسم Variable. في الواقع، يحجب Variable الثاني الأول، ويأخذ أي استخدامات لاسم Variable لنفسه حتى يتم حجبه هو نفسه أو ينتهي Scope. يمكننا حجب Variable باستخدام نفس اسم Variable وتكرار استخدام الكلمة المفتاحية let كما يلي:

اسم الملف: src/main.rs

fn main() {
    let x = 5;

    let x = x + 1;

    {
        let x = x * 2;
        println!("The value of x in the inner scope is: {x}");
    }

    println!("The value of x is: {x}");
}

يربط هذا البرنامج أولاً x بقيمة 5. ثم ينشئ Variable جديد x بتكرار let x = ، آخذاً القيمة الأصلية ومضيفاً 1 بحيث تصبح قيمة x هي 6. ثم، داخل Scope داخلي تم إنشاؤه باستخدام الأقواس المتعرجة، تحجب جملة let الثالثة أيضاً x وتنشئ Variable جديداً، وتضرب القيمة السابقة في 2 لتعطي x قيمة 12. عندما ينتهي هذا Scope، ينتهي الحجب الداخلي ويعود x ليكون 6. عندما نشغل هذا البرنامج، سيخرج ما يلي:

$ cargo run
   Compiling variables v0.1.0 (file:///projects/variables)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s
     Running `target/debug/variables`
The value of x in the inner scope is: 12
The value of x is: 6

يختلف Shadowing عن تمييز Variable كـ mut لأننا سنحصل على Compile-time Error إذا حاولنا بالخطأ إعادة التعيين لهذا Variable دون استخدام الكلمة المفتاحية let. باستخدام let ، يمكننا إجراء بعض التحويلات على قيمة ولكن يظل Variable غير قابل للتغير بعد اكتمال تلك التحويلات.

الفرق الآخر بين mut و Shadowing هو أننا نظراً لأننا نقوم فعلياً بإنشاء Variable جديد عندما نستخدم الكلمة المفتاحية let مرة أخرى، يمكننا تغيير Type القيمة ولكن مع إعادة استخدام نفس الاسم. على سبيل المثال، لنفترض أن برنامجنا يطلب من المستخدم إظهار عدد المسافات التي يريدها بين بعض النصوص عن طريق إدخال أحرف مسافة، ثم نريد تخزين هذا الإدخال كرقم:

fn main() {
    let spaces = "   ";
    let spaces = spaces.len();
}

أول Variable باسم spaces هو من نوع سلسلة نصية (String Type)، وثاني Variable باسم spaces هو من نوع رقمي (Number Type). وبالتالي يجنبنا Shadowing الاضطرار إلى ابتكار أسماء مختلفة، مثل spaces_str و spaces_num؛ بدلاً من ذلك، يمكننا إعادة استخدام اسم spaces الأبسط. ومع ذلك، إذا حاولنا استخدام mut لهذا، كما هو موضح هنا، فسنحصل على Compile-time Error:

fn main() {
    let mut spaces = "   ";
    spaces = spaces.len();
}

يقول الخطأ إنه لا يُسمح لنا بتغيير نوع (Mutate a variable’s type) المتغير:

$ cargo run
   Compiling variables v0.1.0 (file:///projects/variables)
error[E0308]: mismatched types
 --> src/main.rs:3:14
  |
2 |     let mut spaces = "   ";
  |                      ----- expected due to this value
3 |     spaces = spaces.len();
  |              ^^^^^^^^^^^^ expected `&str`, found `usize`

For more information about this error, try `rustc --explain E0308`.
error: could not compile `variables` (bin "variables") due to 1 previous error

الآن بعد أن استكشفنا كيفية عمل Variables، دعنا نلقي نظرة على المزيد من أنواع البيانات (Data Types) التي يمكن أن تمتلكها.