اذهب إلى المحتوى

المتغيرات والتعديل عليها في لغة رست


Naser Dakhel

المتغيرات في رست غير قابلة للتعديل افتراضيًا كما ذكرنا في السابق، وهذه ميزة من ميزات لغة راست التي تهدف إلى جعل شيفرتك البرمجية المكتوبة آمنة وسهلة التزامن concurrency قدر الإمكان، إلا أنها تمنحك خيار التعديل mutable على المتغيرات إذا أردت ذلك. دعنا نرى لماذا تفضّل لغة راست جعل المتغيرات غير قابلة للتعديل في المقام الأول وما هي الحالات التي قد تريد التعديل عليها.

قابلية التعديل على المتغيرات

عندما تُسند أي قيمة إلى اسم متغيرٍ ما للمرة الأولى فهي غير قابلة للتغيير. لتوضيح ذلك دعنا نولّد مشروعًا جديدًا باسم "variables" في مجلد "projects" باستخدام الأمر cargo new variables.

بعد ذلك، افتح افتح الملف "src/main.rs" في المجلد "variables" الجديد، واستبدل الشيفرة البرمجية الموجودة داخله بالشيفرة التالية. لن تُصرّف الشيفرة البرمجية هذه بعد، وسنفحص أولًا خطأ عدم قابلية التعديل immutability error.

اسم الملف: 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، إذ يجب أن تتلقى رسالة خطأ كما هو موضّح في الخرج التالي:

$ 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`
  |         help: consider making this binding mutable: `mut x`
3 |     println!("The value of x is: {x}");
4 |     x = 6;
  |     ^^^^^ cannot assign twice to immutable variable

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

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

تُشير رسالة الخطأ "cannot assign twice immutable variablex" إلى أنك لا تستطيع إعادة إسناد قيمة أخرى إلى المتغير غير القابل للتعديل x.

من المهم الحصول على أخطاء عند تصريف الشيفرة البرمجية عندما نحاول تعديل قيمة من المُفترض أن تكون غير قابلة للتعديل، فقد يؤدي هذا السلوك إلى أخطاء داخل البرنامج، ومن الممكن أن تتوقف بعض أجزاء البرنامج عن العمل بصورةٍ صحيحة إذا كان جزء من أجزاء برنامجك يعمل وفق الافتراض أن القيمة لن تتغير أبدًا وغيّر جزءٌ آخر هذه القيمة، وهذا النوع من الأخطاء صعب التعقّب عادةً بالأخص عندما يكون الجزء الثاني من البرنامج يُغيّر القيمة فقط في بعض الحالات. يضمن لك مصرّف لغة راست أن القيمة لن تتغيّر إذا حدّدتَ أنها لن تتغير، لذا لن يكون عليك تعقّب هذا النوع من الأخطاء بنفسك، وستكون شيفرتك البرمجية سهلة الفهم.

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

على سبيل المثال، دعنا نغير محتويات الملف "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 [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. يعتمد استخدام -أو عدم استخدام- قابلية التعديل على الشيء الأكثر وضوحًا في كل حالة بنظرك.

الثوابت

الثوابت constants هي قيم مُسندة إلى اسم ما ولا يُمكن تغييرها كما هو الحال في المتغيرات غير القابلة للتعديل، إلا أن هناك بعض الفروقات بين الثوابت والمتغيرات.

أولًا، ليس من المسموح استخدام mut مع الثوابت، إذ أنها غير قابلة للتعديل افتراضيًا وهي الحالة الدائمة لها، نصرّح ثابتًا باستخدام الكلمة المفتاحية const بدلًا من let ويجب تحديد نوع القيمة عندها. سنغطّي أنواع البيانات لاحقًا، لذا لا تقلق بخصوص التفاصيل الآن، فكل ما يجب معرفته هو أنه يجب علينا تحديد النوع دائمًا مع الثوابت.

ثانيًا، يُمكن التصريح عن الثوابت ضمن أي نطاق بما فيه النطاق العام global scope الذي يجعل منها قيمًا يمكن الاستفادة منها لأجزاء مختلفة من البرنامج.

ثالثًا، يمكن أن تُسند الثوابت فقط إلى تعابير ثابتة وليس إلى قيمة تُحسب عند تشغيل البرنامج.

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

const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;

اسم الثابت هو "THREE_HOURS_IN_SECONDS" وتُسند قيمته إلى ناتج ضرب 60 (الثواني في الدقيقة) مع 60 (الدقائق في الساعة) مع 3 (عدد الساعات التي نريد حسابها في هذا البرنامج)، تُسمّى الثوابت في لغة رست اصطلاحًا باستخدام أحرف كبيرة وشرطة سفلية underscore بين كل كلمة وأخرى. يستطيع المصرّف في هذه الحالة تقييم ناتج العمليات عند تصريف البرنامج، مما يسمح لنا بكتابة القيمة بطريقة أكثر بساطة وأسهل فهمًا للقارئ من كتابة القيمة 10,800 فورًا.

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

تٌفيدك عملية تسمية القيم المُستخدمة ضمن برنامجك مثل ثوابت في معرفة معنى القيمة لكل من يقرأ شيفرتك البرمجية، كما تُساعد في وجود القيمة في مكان واحد ضمن برنامجك بحيث يسهل عليك تعديلها مرةً واحدةً إذا أردت في المستقبل.

التظليل

يُمكنك التصريح عن متغير جديد يحمل اسمًا مماثلًا لمتغير آخر سابق كما رأينا في المقال السابق الذي ناقش كيفية برمجة لعبة تخمين، ويقول مبرمجو اللغة عادةً أن المتغير الأوّل تظلّل shadowed بالثاني، مما يعني أن المتغير الثاني هو ما سيجده المُصرّف عندما تستخدم اسم المتغير من النقطة تلك فصاعدًا. بالمثل، فالمتغير الثاني ظَلّل overshadow الأول آخذًا استخدامات اسم المتغير لنفسه حتى يُظلّل المتغير الثاني بنفسه أو ينتهي النطاق الذي ينتمي إليه. يمكننا تظليل متغير ما باستخدام اسم المتغير ذاته وإعادة استخدامه مع الكلمة المفتاحية 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، ثم يُنشئ متغيرًا جديدًا x بتكرار let x =‎ آخذًا القيمة الأصلية ومُضيفًا إليها 1، وبالتالي تصبح قيمة x مساويةً إلى 6، ثم تُظلّل تعليمة let الثالثة ضمن النطاق الداخلي المحتوى داخل الأقواس المعقوصة curly brackets المتغير x وتنشئ متغير x مجددًا وتضرب قيمته السابقة بالرقم 2، فتصبح قيمة المتغير 12، وعند نهاية النطاق ينتهي التظليل الداخلي ويعود المتغير x إلى قيمته السابقة 6. نحصل على الخرج التالي عند تشغيل البرنامج:

$ cargo run
   Compiling variables v0.1.0 (file:///projects/variables)
    Finished dev [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

التظليل مختلف عن استخدام الكلمة المفتاحية mut لأنك ستحصل على خطأ تصريفي compile-time error إذا حاولت إعادة إسناد قيمة إلى هذا المتغير عن طريق الخطأ دون استخدام الكلمة المفتاحية let. يمكننا إجراء عدة تغييرات على قيمة ما باستخدام let وجعل المتغير غير قابل للتعديل بعد هذه استكمال هذه التغييرات.

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

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

متغير spaces الأول من نوع سلسلة نصية string بينما المتغير spaces الآخر من نوع عددي، إذًا، يوفّر علينا التظليل هنا عناء إنشاء متغير باسم جديد، مثل spaces_str و spaces_num، ويمكننا بدلًا من ذلك إعادة استخدام الاسم "spaces"، إلا أننا سنحصل على خطأ تصريفي إذا حاولنا استخدام mut في هذه الحالة:

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

يُشير الخطأ إلى أنه من غير المسموح التعديل على نوع المتغير:

$ 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` due to previous error

الآن وبعد رؤيتنا لكيفية عمل المتغيرات، سننظر إلى أنواع البيانات الأخرى التي يمكننا استخدامها.

ترجمة -وبتصرف- للقسم Variables and Mutability من كتاب The Rust Programming Language.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...