تُعد القدرة على تشغيل جزء من الشيفرة البرمجية إذا تحقق شرط ما، أو تشغيل جزء ما باستمرار بينما الشرط محقق من الكتل الأساسية في بناء أي لغة برمجة، كما تُعد تعابير if
والحلقات التكرارية أكثر اللبنات التي تسمح لك بالتحكم بسير تنفيذ البرنامج flow control في البرامج المكتوبة بلغة راست.
تعابير if الشرطية
يسمح لك تعبير if
بتفرعة branch شيفرتك البرمجية بحسب الشروط، ويُمكنك كتابة الشرط بحيث "إذا تحقق هذا الشرط فنفذ هذا الجزء من الشيفرة البرمجية، وإلا فلا تنفّذه".
أنشئ مشروعًا جديدًا باسم "branches" في المجلد "projects"، إذ سنستخدم هذا المشروع للتعرف على تعابير if
. عدّل الملف "src/main.rs" ليحتوي على الشيفرة التالية:
اسم الملف: src/main.rs
fn main() { let number = 3; if number < 5 { println!("condition was true"); } else { println!("condition was false"); } }
تبدأ جميع تعابير if
بالكلمة المفتاحية if
متبوعةً بالشرط، ويتحقق الشرط في مثالنا السابق فيما إذا كانت قيمة المتغير number
أصغر من 5، ونضع شيفرة برمجية مباشرةً بعد الشرط داخل أقواس معقوصة تُنفّذ إذا كان الشرط المذكور محققًا، تُدعى الشيفرة البرمجية المُرتبطة بالشرط في تعابير if
بالأذرع arms في بعض الأحيان، وهي تشبه أذرع تعابير match
التي تكلمنا عنها سابقًا.
يُمكننا أيضًا تضمين تعبير else
اختياريًا وهو ما فعلناه في هذا المثال، وذلك لإعطاء البرنامج كتلة بديلة للتنفيذ إذا كان الشرط في تعليمة if
السابقة غير مُحقّق. يتخطى البرنامج كتلة تعليمة if
ببساطة وينفذ بقية البرنامج في حال عدم وجود تعبير else
.
نحصل على الخرج التالي في حال تجربتنا لتنفيذ الشيفرة البرمجية:
$ cargo run Compiling branches v0.1.0 (file:///projects/branches) Finished dev [unoptimized + debuginfo] target(s) in 0.31s Running `target/debug/branches` condition was true
دعنا نُغيّر قيمة number
إلى قيمة أخرى تجعل قيمة الشرط false
ونرى ما الذي سيحدث:
let number = 7;
نفّذ البرنامج مجددًا، وانظر إلى الخرج:
$ cargo run Compiling branches v0.1.0 (file:///projects/branches) Finished dev [unoptimized + debuginfo] target(s) in 0.31s Running `target/debug/branches` condition was false
من الجدير بالذكر أيضًا أن الشرط في هذه الشيفرة البرمجية يجب أن يكون من النوع bool
وإذا لم يكن كذلك فسنحصل على خطأ، جرّب تنفيذ الشيفرة البرمجية التالية على سبيل المثال:
اسم الملف: src/main.rs
fn main() { let number = 3; if number { println!("number was three"); } }
يُقيَّم شرط if
إلى القيمة 3 هذه المرة، ويعرض لنا راست الخطأ التالي:
$ cargo run Compiling branches v0.1.0 (file:///projects/branches) error[E0308]: mismatched types --> src/main.rs:4:8 | 4 | if number { | ^^^^^^ expected `bool`, found integer For more information about this error, try `rustc --explain E0308`. error: could not compile `branches` due to previous error
يُشير الخطأ إلى أن راست توقّع تلقي قيمة من نوع bool
إلا أنه حصل على قيمة عدد صحيح integer. لا تُحوّل راست الأنواع غير البوليانية إلى أنواع بوليانية بعكس لغات البرمجة الأخرى، مثل روبي وجافا سكريبت، إذ عليك أن تكون دقيقًا بكتابة شرط تعليمة if
ليكون تعبيرًا يُقيَّم إلى قيمة بوليانية، على سبيل المثال إن أردنا لكتلة تعليمة if
أن تعمل فقط في حالة كان الرقم لا يساوي الصفر فيمكننا تغيير التعبير كما يلي:
اسم الملف: src/main.rs
fn main() { let number = 3; if number != 0 { println!("number was something other than zero"); } }
سيطبع تشغيل الشيفرة البرمجية السابقة "number was something other than zero".
التعامل مع عدة شروط باستخدام else if
يُمكنك استخدام عدة شروط باستخدام if
و else
في تعابير else if
، على سبيل المثال:
اسم الملف: src/main.rs
fn main() { let number = 6; if number % 4 == 0 { println!("number is divisible by 4"); } else if number % 3 == 0 { println!("number is divisible by 3"); } else if number % 2 == 0 { println!("number is divisible by 2"); } else { println!("number is not divisible by 4, 3, or 2"); } }
لهذا البرنامج أربعة مسارات مختلفة ممكنة التنفيذ، ومن المُفترض أن تحصل على الخرج التالي بعد تشغيله:
$ cargo run Compiling branches v0.1.0 (file:///projects/branches) Finished dev [unoptimized + debuginfo] target(s) in 0.31s Running `target/debug/branches` number is divisible by 3
يتحقَّق البرنامج عند تنفيذه من كل تعبير if
فيما إذا كان مُحقًًقًا ويُنفّذ أول متن شرط يُحقّق، لاحظ أنه على الرغم من قابلية قسمة 6 على 2 إلا أننا لم نرى الخرج number is divisible by 2
، أو الخرج number is not divisible by 4, 3, or 2
من كتلة التعليمة else
، وذلك لأن راست تُنفّذ الكتلة الأولى التي تحقق الشرط فقط وحالما تجد هذه الكتلة، فإنها لا تتفقّد تحقق الشروط الأخرى التي تلي تلك الكتلة.
قد يسبب استخدام الكثير من تعابير else if
الفوضى في شيفرتك البرمجية، لذا إذا كان لديك أكثر من تعبير واحد، تأكد من إعادة النظر إلى شيفرتك البرمجية ومحاولة تحسينها، وسنناقش لاحقًا هيكل تفرعي branching construct في راست يُدعى match
وقد صُمّم لهذه الحالات خصيصًا.
استخدام if في تعليمة let
يُمكننا استخدام if
في الجانب الأيمن من تعليمة let
بالنظر إلى أنها تعبير وإسناد النتيجة إلى متغير كما توضح الشيفرة 3-2.
اسم الملف: src/main.rs
fn main() { let condition = true; let number = if condition { 5 } else { 6 }; println!("The value of number is: {number}"); }
[الشيفرة 3-2: إسناد نتيجة تعبير if إلى متغير]
يُسند المتغير number
إلى قيمة بناءً على نتيجة تعبير if
، نفّذ الشيفرة البرمجية السابقة ولاحظ النتيجة:
$ cargo run Compiling branches v0.1.0 (file:///projects/branches) Finished dev [unoptimized + debuginfo] target(s) in 0.30s Running `target/debug/branches` The value of number is: 5
تذكر أن كُتل الشيفرات البرمجية تُقيّم إلى قيمة آخر تعبير موجود داخلها والأرقام بحد ذاتها هي تعابير أيضًا، في هذه الحالة تعتمد قيمة تعبير if
بالكامل على أي كتلة برمجية تُنفَّذ، وهذا يعني أنه يجب أن تكون القيم المُحتمل أن تكون نتيجة تعبير if
من النوع ذاته.
نجد في الشيفرة 2 نتيجة كل من ذراع if
و else
، إذ تُمثّل القيمة النوع الصحيح i32
. نحصل على خطأ إذا كانت الأنواع غير متوافقة كما هو الحال في المثال التالي:
اسم الملف: src/main.rs
fn main() { let condition = true; let number = if condition { 5 } else { "six" }; println!("The value of number is: {number}"); }
عندما نحاول تصريف الشيفرة البرمجية السابقة سنحصل على خطأ، إذ يوجد لذراعي if
و else
قيمتين من أنواع غير متوافقة، ويدلّنا راست على مكان المشكلة في البرنامج بالضبط عن طريق الرسالة:
$ cargo run Compiling branches v0.1.0 (file:///projects/branches) error[E0308]: `if` and `else` have incompatible types --> src/main.rs:4:44 | 4 | let number = if condition { 5 } else { "six" }; | - ^^^^^ expected integer, found `&str` | | | expected because of this For more information about this error, try `rustc --explain E0308`. error: could not compile `branches` due to previous error
يُقيَّم التعبير الموجود في كتلة if
إلى عدد صحيح، بينما يُقيَّم التعبير الموجود في كتلة else
إلى سلسلة نصية، وذلك لن يعمل لأنه يجب على المتغيرات أن تكون من النوع ذاته وذلك حتى تعرف راست نوع المتغير number
وقت التصريف بصورةٍ نهائية ومؤكدة، إذ تسمح معرفة نوع number
للمصرف بالتحقق من أن النوع المُستخدم صالح الاستخدام في كل مكان نستخدم فيه المتغير number
، ولن تكون راست قادرةً على التحقق من هذا الأمر إذا كان نوع المتغير number
يُحدّد عند وقت التشغيل runtime فقط، إذ سيُصبح المصرف مُشوَّشًا ولن يُقدم الضمانات ذاتها في الشيفرة البرمجية إذا كان عليه تتبع عدة أنواع افتراضية لأي متغير.
التكرار باستخدام الحلقات
نحتاج غالبًا لتنفيذ جزء محدد من الشيفرة البرمجية أكثر من مرة واحدة، ولتحقيق ذلك تزوّدنا راست بالحلقات التي تُنفّذ الشيفرة البرمجية داخل متن الحلقة من البداية إلى النهاية ومن ثم إلى البداية مجددًا، وللتعرّف إلى الحلقات دعنا نُنشئ مشروعًا جديدًا باسم "loops".
لراست ثلاثة أنواع من الحلقات، هي: loop
و while
و for
، دعنا نجرّب كل منها.
تكرار الشيفرة البرمجية باستخدام loop
تُعلِم الكلمة المفتاحية loop
راست بوجوب تنفيذ جزء من الشيفرة البرمجية على نحوٍ متكرر إلى الأبد أو لحين تحديد التوقف بصورةٍ صريحة.
على سبيل المثال، عدّل محتويات الملف "src/main.rs" في مجلد مشروعنا الجديد "loops" ليحتوي على الشيفرة البرمجية التالية:
اسم الملف: src/main.rs
fn main() { loop { println!("again!"); } }
عندما نُشغّل البرنامج السابق سنجد النص "again!" مطبوعًا مرةً بعد الأخرى باستمرار إلى أن نوقف البرنامج يدويًا، ونستطيع إيقافه باستخدام اختصار لوحة المفاتيح "ctrl-c"، إذ تدعم معظم الطرفيات هذا الاختصار لإيقاف البرنامج في حال تكرار حلقة للأبد. جرّب الأمر:
$ cargo run Compiling loops v0.1.0 (file:///projects/loops) Finished dev [unoptimized + debuginfo] target(s) in 0.29s Running `target/debug/loops` again! again! again! again! ^Cagain!
يُمثل الرمز ^C
الموضع الذي ضغطت فيه على الاختصار "ctrl-c"، وقد تجد الكلمة again!
مطبوعةً بعد ^C
أو قد لا تجدها بحسب مكان التنفيذ ضمن الشيفرة البرمجية عند ضغطك على إشارة المقاطعة interrupt signal.
تُزوّدنا راست أيضًا لحسن الحظ بطريقة أخرى للخروج قسريًا من حلقة تكرارية باستخدام شيفرة برمجية، إذ يمكننا استخدام الكلمة المفتاحية break
داخل الحلقة التكرارية لإخبار البرنامج بأننا نريد إيقاف تنفيذ الحلقة. تذكر أننا فعلنا ذلك عند كتابتنا شيفرة برنامج لعبة التخمين سابقًا وذلك للخروج من البرنامج عندما يفوز اللاعب بتخمين الرقم الصحيح.
كما أننا استخدمنا أيضًا الكلمة المفتاحية continue
في لعبة التخمين، وهي كلمة تُخبر البرنامج بتخطي أي شيفرة برمجية متبقية داخل الحلقة في التكرار الحالي والذهاب إلى التكرار اللاحق.
إعادة قيم من الحلقات
واحدة من استخدامات loop
هي إعادة تنفيذ عملية قد تفشل، مثل التحقق إذا أنهى خيط thread ما العمل، وقد تحتاج أيضًا إلى تمرير نتيجة هذه العملية خارج الحلقة إلى باقي الشيفرة البرمجية؛ ولتحقيق ذلك يمكنك إضافة القيمة التي تُريد إعادتها بعد تعبير break
، إذ سيتوقف عندها تنفيذ الحلقة وستُعاد القيمة خارج الحلقة حتى يتسنى لك استخدامها كما هو موضح:
fn main() { let mut counter = 0; let result = loop { counter += 1; if counter == 10 { break counter * 2; } }; println!("The result is {result}"); }
نُصرّح عن متغير باسم counter
ونُهيّئه بالقيمة "0" قبل الحلقة التكرارية، ثم نصرح عن متغير باسم result
لتخزين القيمة المُعادة من الحلقة. نُضيف 1 إلى المتغير counter
عند كل تكرار للحلقة ومن ثم نتحقق فيما إذا كان المتغير counter
مساويًا إلى القيمة 10، وعندما يتحقق هذا الشرط نستخدم الكلمة المفتاحية break
مع القيمة counter * 2
، ونستخدم بعد الحلقة فاصلة منقوطة لإنهاء التعليمة التي تُسند القيمة إلى result
، وأخيرًا نطبع القيمة result
التي تكون في هذه الحالة مساويةً إلى 20.
تسمية الحلقات للتفريق بين عدة حلقات
تُطبّق break
و continue
في حال وجود حلقة داخل حلقة على الحلقة الداخلية الموجود بها الكلمة المفتاحية، ويمكنك تحديد تسمية الحلقة loop label اختياريًا عند إنشاء حلقة حتى يُمكنك استخدام break
أو continue
مع تحديد تسمية الحلقة بدلًا من تنفيذ عملها على الحلقة الداخلية. يجب أن تبدأ تسمية الحلقة بعلامة تنصيص واحدة، ويوضح المثال التالي استخدام حلقتين متداخلتين:
fn main() { let mut count = 0; 'counting_up: loop { println!("count = {count}"); let mut remaining = 10; loop { println!("remaining = {remaining}"); if remaining == 9 { break; } if count == 2 { break 'counting_up; } remaining -= 1; } count += 1; } println!("End count = {count}"); }
للحلقة الخارجية التسمية 'counting_up
وستعدّ من 0 إلى 2، بينما تعدّ الحلقة الداخلية عديمة التسمية من 10 إلى 9. لا تُحدد break
الأولى أي تسمية لذلك ستغادر الحلقة الداخلية فقط، بينما ستغادر تعليمة break 'counting_up
الحلقة الخارجية، وتطبع الشيفرة البرمجية السابقة ما يلي:
$ cargo run Compiling loops v0.1.0 (file:///projects/loops) Finished dev [unoptimized + debuginfo] target(s) in 0.58s Running `target/debug/loops` count = 0 remaining = 10 remaining = 9 count = 1 remaining = 10 remaining = 9 count = 2 remaining = 10 End count = 2
الحلقات الشرطية باستخدام while
سيحتاج البرنامج غالبًا إلى تقييم شرط داخل حلقة، بحيث يستمر تنفيذ الحلقة إذا كان الشرط محققًا وإلا فسيتوقف تنفيذها عن طريق استدعاء break
وإيقاف الحلقة ومن الممكن تطبيق شيء مماثل باستخدام مزيج من loop
و if
و else
و break
، ويمكنك تجربة الأمر الآن داخل برنامج إذا أردت ذلك. يُعد هذا النمط شائعًا جدًا وهذا هو السبب وراء وجود بنية مُضمَّنة في راست لهذا الاستخدام تُدعى حلقة while
. نستخدم في الشيفرة 3-3 التالية الحلقة while
لتكرار الحلقة ثلاث مرات بالعدّ تنازليًا في كل مرة وعند الخروج من الحلقة نطبع رسالة ونُنهي البرنامج.
اسم الملف: src/main.rs
fn main() { let mut number = 3; while number != 0 { println!("{number}!"); number -= 1; } println!("LIFTOFF!!!"); }
[الشيفرة 3-3: استخدام حلقة while لتنفيذ شيفرة برمجية عند تحقق شرط ما]
يُغنينا استخدام هذه البنية عناء استخدام الكثير من التداخلات بواسطة loop
و if
و else
و break
كما أنه أكثر وضوحًا، إذ طالما يكون الشرط محققًا ستُنفّذ الحلقة وإلا فسيغادر الحلقة.
استخدام for مع تجميعة Collection
يمكنك اختيار البنية while
للانتقال بين عناصر التجميعة مثل المصفوفات. توضح الشيفرة 3-4 ذلك الاستخدام بطباعة كل عنصر في المصفوفة a
.
اسم الملف: src/main.rs
fn main() { let a = [10, 20, 30, 40, 50]; let mut index = 0; while index < 5 { println!("the value is: {}", a[index]); index += 1; } }
[الشيفرة 4: الانتقال بين عناصر التجميعة باستخدام حلقة while]
إليك الشيفرة البرمجية التي تنتقل بين عناصر المصفوفة، إذ تبدأ من الدليل "0" وتنتقل إلى ما يليه لحد الوصول إلى الدليل الأخير في المصفوفة (أي عندما يكون index < 5
غير محقق). سيطبع تنفيذ الشيفرة السابقة عناصر المصفوفة كما يلي:
$ cargo run Compiling loops v0.1.0 (file:///projects/loops) Finished dev [unoptimized + debuginfo] target(s) in 0.32s Running `target/debug/loops` the value is: 10 the value is: 20 the value is: 30 the value is: 40 the value is: 50
تظهر جميع قيم عناصر المصفوفة الخمسة ضمن الطرفية كما هو متوقع. على الرغم من أن index
سيصل إلى القيمة 5 في مرحلة ما إلا أن تنفيذ الحلقة يتوقف قبل محاولة طباعة العنصر السادس من المصفوفة.
سلوك البرنامج معرض للخطأ، فقد يهلع البرنامج إذا كانت قيمة الدليل أو الشرط الذي يُفحص خاطئة، على سبيل المثال إذا استبدلنا تعريف المصفوفة a
ليكون لها أربعة عناصر ولكننا نسينا تحديث الشرط إلى while index < 4
، ستهلع الشيفرة البرمجية، كما أن هذا السلوك بطيء لأن المصرف يُضيف شيفرة برمجية عند وقت التشغيل لإنجاز التحقق من الشرط فيما إذا كان الدليل خارج حدود المصفوفة عند كل تكرار ضمن الحلقة. بدلًا من ذلك، يمكننا استخدام حلقة for
وتنفيذ شيفرة برمجية لكل عنصر في التجميعة، وتبدو الحلقة بالشكل الموضح في الشيفرة 3-5.
اسم الملف: src/main.rs
fn main() { let a = [10, 20, 30, 40, 50]; for element in a { println!("the value is: {element}"); } }
[الشيفرة 3-5: الانتقال بين عناصر التجميعة باستخدام حلقة for]
ستجد الخرج ذاته للشيفرة 3-4 عند تنفيذ الشيفرة السابقة، والأهم هنا أننا زدنا من أمان شيفرتنا البرمجية وأزلنا أي فرص للأخطاء الناجمة عن الذهاب إلى ما بعد حدود المصفوفة، أو عدم الذهاب إلى نهايتها وبالتالي عدم طباعة جميع العناصر.
لست مضطرًا لتغيير أي شيفرة برمجية باستخدام حلقة for
إذا عدلت رقم العناصر في المصفوفة، الأمر الذي ستضطر لفعله في حال استخدامك للشيفرة 3-4.
تُستخدم حلقات for
كثيرًا نظرًا للأمان والإيجاز التي تقدمه مقارنةً ببُنى الحلقات الأخرى الموجودة في راست، حتى أن معظم مبرمجين لغة راست يفضلون استخدام الحلقة for
عند تنفيذ شيفرة برمجية يُفترض تنفيذها عدد معين من المرات كما هو الحال في مثال العد التنازلي الذي أنجزناه باستخدام حلقة while
في الشيفرة 3-3، ويُنجز ذلك الأمر باستخدام Range
المُضمَّن في المكتبة القياسية، والذي يولّد بدوره جميع الأرقام في السلسلة بدءًا من رقم معين وانتهاءً برقم آخر.
إليك ما سيبدو عليه برنامج العد التنازلي باستخدام حلقة for
وتابع آخر لم نتكلم عنه بعد وهو rev
، المُستخدم في عكس المجال:
اسم الملف: src/main.rs
fn main() { for number in (1..4).rev() { println!("{number}!"); } println!("LIFTOFF!!!"); }
تبدو هذه الشيفرة البرمجية أفضل، أليس كذلك؟
ترجمة -وبتصرف- للقسم Control Flow من كتاب The Rust Programming Language.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.