دعنا نتعرّف إلى رست بالعمل على مشروع عملي سويًّا، إذ سيقدم هذا المقال بعض المفاهيم الشائعة في رست وكيفية استخدامها في برامج حقيقية، وسنتعلم كل من let
و match
والتوابع methods والدوال المترابطة associated functions واستخدام صناديق crates خارجية والمزيد، وسنناقش هذه التفاصيل بتعمق أكبر في مقالات لاحقة، إلا أننا سنتدرب على الأساسيات في هذا المقال.
سنعمل على برنامج بسيط وشائع للمبتدئين ألا وهو لعبة تخمين. إليك كيف سيعمل البرنامج: سيولّد البرنامج رقمًا صحيحًا عشوائيًا بين 1 و100، ثمّ سينتظر من اللاعب إدخال التخمين، ثم سيجيب البرنامج فيما إذا كان التخمين أكبر أو أصغر من الإجابة، وفي حال كان التخمين صحيحًا، سيطبع البرنامج رسالة تهنئة وينتهي البرنامج.
إعداد المشروع الجديد
اذهب إلى مجلد "directory" الذي أنشأناه في المقال السابق لإعداد المشروع الجديد باستخدام أداة كارجو Cargo كما يلي:
$ cargo new guessing_game $ cd guessing_game
يأخذ الأمر الأول cargo new
اسم المشروع، وهو في حالتنا "guessing_game" مقل وسيطٍ أول، بينما ينتقل الأمر الثاني إلى مجلد المشروع.
ألقِ نظرةً إلى محتويات الملف "Cargo.toml" الناتج:
[package] name = "guessing_game" version = "0.1.0" edition = "2021" # يمكنك رؤية المزيد من المفاتيح من الرابط https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies]
كما رأينا في المقال السابق، يولّد الأمر cargo new
برنامج "!Hello, world" لك. ألقِ نظرةً على محتويات ملف "src/main.rs":
fn main() { println!("Hello, world!"); }
دعنا الآن نصرّف هذا البرنامج ونشغله باتباع نفس الخطوة وباستخدام أمر cargo run
:
$ cargo run Compiling guessing_game v0.1.0 (file:///projects/guessing_game) Finished dev [unoptimized + debuginfo] target(s) in 1.50s Running `target/debug/guessing_game` Hello, world!
تبرز أهمية الأمر run
عندما تريد أن تفحص التغييرات في مشروعك تباعًا وأن تفحص البرنامج بسرعة بعد كل إضافة قبل المضيّ قدمًا للإضافة التالية وهذا ما سنفعله بالضبط في لعبتنا هذه.
أعِد الآن فتح الملف "src/main.rs"، إذ سنكتب الشيفرة البرمجية لمشروعنا فيه.
معالجة التخمين
الجزء الأول من برنامج لعبة التخمين هو سؤال المستخدم ليدخل التخمين، ومن ثم معالجة هذا الدخل والتحقق من أنه بتنسيق مناسب. دعنا بدايةً نسمح المستخدم بإدخال تخمين. اكتب الشيفرة التالية في الملف "src/main.rs":
use std::io; fn main() { println!("Guess the number!"); println!("Please input your guess."); let mut guess = String::new(); io::stdin() .read_line(&mut guess) .expect("Failed to read line"); println!("You guessed: {guess}"); }
[شيفرة 2-1: شيفرة برمجية تأخذ التخمين من المستخدم في الدخل وتطبعه]
تحتوي الشيفرة البرمجية السابقة على كثيرٍ من المعلومات الجديدة، لذلك دعنا نراجعها سطرًا بسطر. علينا أن نستخدم المكتبة io
وأن نضيفها إلى نطاق scope المشروع للحصول على دخل المستخدم ومن ثم طباعة نتيجة الدخل في الخرج؛ إذ تأتي مكتبة io
مع المكتبة القياسية، التي تُعرف باسم std
:
use std::io;
تحتوي رست افتراضيًا على مجموعةٍ معرفةٍ من العناصر ضمن المكتبة القياسية التي تُضاف إلى نطاق أي برنامج، وتُسمى هذه العناصر باسم المقدمة prelude ويمكنك رؤية جميع محتوياتها في توثيق المكتبة القياسية.
إذا أردت استخدام نوع محدد غير متواجد في المقدمة، فعليك إضافته إلى النطاق عن طريق استخدام تعليمة use
. تتيح لك مكتبة std::io
استخدام عدد من المزايا المفيدة منها القدرة على تلقّي دخل المستخدم.
دالة main
هي النقطة التي يبدأ منها البرنامج كما رأينا في المقال السابق:
fn main() {
تصرّح الصيغة fn
عن دالة جديدة وتُشير الأقواس ()
إلى أن الدالة لا تأخذ أي معاملات، ويُشير القوس المعقوص }
إلى بداية متن الدالة.
تشير println!
إلى ماكرو كما تطرقنا إلى ذلك في المقال السابق ويطبع هذا الماكرو السلسلة النصية إلى الشاشة:
println!("Guess the number!"); println!("Please input your guess.");
تطبع الشيفرة السابقة جملةً تدلّ المستخدم على ماهية اللعبة ومن ثم جملة تطلب منه إدخالًا.
تخزين القيم والمتغيرات
نُنشئ الآن متغيرًا لتخزين دخل المستخدم كما يلي:
let mut guess = String::new();
أصبح الآن برنامجنا أكثر إثارةً للاهتمام؛ فهناك الكثير من الأشياء التي تحدث عند تنفيذ هذا السطر القصير، إذ نستخدم تعليمة let
لإنشاء متغير، إليك مثالًا آخر عن إنشاء متغير:
let apples = 5;
يُنشئ هذا السطر متغيرًا جديدًا باسم apples ويُسنده إليه القيمة 5. المتغيرات في لغة رست ثابتة immutable افتراضيًا، وهذا يعني أن المتغير سيحافظ على قيمته الأولية التي أُسندت إليه ولن تُغيّر وسنتحدث في هذا الموضوع بتوسع أكثر لاحقًا. لجعل المتغير قابلًا للتغيير mutable نستخدم الكلمة المفتاحية mut
قبل اسم المتغير:
let apples = 5; // ثابت let mut bananas = 5; // متغيّر
لاحظ أن //
تتسبب ببدء تعليق سطري ينتهي بنهاية السطر وتتجاهل رست كل ما ورد ضمن التعليق، وسنناقش التعليقات لاحقًا بتوسع أكبر.
بالعودة إلى برنامج لعبة التخمين، فأنت تعلم الآن أن let mut guess
سيُضيف متغيرًا يقبل التغيير باسم guess، وتُخبر إشارة المساواة =
رست أنّنا نريد إسناد قيمة ما إلى المتغير، يقع على يمين إشارة المساواة القيمة التي نريد إسنادها إلى guess
وهي قيمةٌ ناتجةٌ عن استدعاء الدالة String::new
وهي دالة تُعيد نُسخةً instance من النوع "String"؛ وهو نوع من أنواع السلاسل النصية الموجود في المكتبة القياسية وهو نص بترميز UTF-8 وقابل للزيادة.
تُشير ::
في السطر new::
إلى أن new
مرتبطةٌ بدالة من نوع String؛ والدالة المرتبطة associated function هي دالة تُطبّق على نوع ما -وفي هذه الحالة هو String- وتُنشئ الدالة new
هذه سلسلةً نصيةً جديدةً وفارغة، وستجد دالة new
هذه في العديد من الأنواع لأنه اسم شائع لدالة تُنشئ قيمةً جديدةً من نوعٍ ما.
إذا نظرنا إلى السطر let mut guess = String::new();
كاملًا، فهو سطرٌ لإنشاء متغير قابل للتغيير مُسندٌ إلى نُسخة جديدة وفارغة من النوع String.
تلقي دخل المستخدم
تذكر أننا ضمّننا إمكانية تلقي الدخل وعرض الخرج عن طريق use std::io;
من المكتبة القياسية في السطر الأول من البرنامج. دعنا الآن نستدعي دالة stdin
من وحدة io
التي ستسمح لنا بالتعامل مع دخل المستخدم:
io::stdin() .read_line(&mut guess)
يمكننا استخدام استدعاء الدالة السابقة حتى لو لم نستورد مكتبة io
بكتابتنا use std::io
في بداية البرنامج ولكن الاستدعاء حينها سيكون بالشكل std::io::stdin
. تُعيد الدالة stdin
نسخة من النوع std::io::Stdin
وهو نوع يُمثّل مُعالجًا للدخل القياسي من الطرفية.
يستدعي السطر .read_line(&mut guess)
تابع read_line
ضمن معالج الدخل القياسي للحصول على دخل المستخدم، كما أننا نمرّر &mut guess
مثل وسيط إلى read_line
للدلالة على السلسلة النصية التي سيُخزّن بها دخل المستخدم. تتمثّل وظيفة read_line
بأخذ ما يكتبه المستخدم إلى الدخل القياسي وإلحاقه append بالسلسلة النصية (دون الكتابة فوق overwriting محتوياته)، ولذا فنحن نمرّر هنا السلسلة النصية وسيطًا، ويجب أن يكون الوسيط قابلًا للتغيير حتى يكون التابع قادرًا على تغيير محتويات السلسلة النصية.
يُشير الرمز &
إلى أن هذا الوسيط يمثل مرجعًا، وهي طريقةٌ تسمح لأجزاء مختلفة من شيفرتك البرمجية بالوصول إلى الجزء ذاته من البيانات دون الحاجة إلى نسخ البيانات إلى الذاكرة عدة مرات. تُعد ميزة المراجع ميزةً معقّدةً وأكبر ميزات رست هو مستوى الأمان العالي وسهولة استخدام المراجع. لا تحتاج لمعرفة المزيد من هذه التفاصيل حتى تُنهي كتابة هذا البرنامج، إذ يكفي للآن أن تعرف أن المراجع غير قابلة للتغيير افتراضيًا -كما هو الحال في المتغيرات- وبالتالي يجب عليك كتابة &mut guess
بدلًا من &guess
إذا أردت جعلها قابلة للتغيير (سنشرح لاحقًا المراجع باستفاضة).
التعامل مع الأخطاء الممكنة باستخدام نوع النتيجة Result
ما زلنا نعمل على السطر البرمجي ذاته، ونناقش الآن السطر الثالث من النص، إلا أنه يجب الملاحظة أنه يمثل جزءًا من السطر البرمجي المنطقي ذاته. يمثل الجزء الثالث التابع:
.expect("Failed to read line");
كان بإمكانك كتابة السطر البرمجي على النحو التالي:
io::stdin().read_line(&mut guess).expect("Failed to read line");
إلا أن قراءة سطر طويل عملية صعبةٌ ومن الأفضل تقسيمه لأجزاء، لذلك من المحبّذ استخدام سطور جديدة ومسافات فارغة أخرى لتجزئة السطور الطويلة عندما تستدعي تابعًا على النحو التالي: .method_name()
. دعنا الآن نناقش عمل السطر هذا.
تضع الدالة read_line
كل ما يكتبه المستخدم إلى السلسلة النصية التي نمررها لها كما ذكرنا سابقًا، إلا أنها تُعيد أيضًا قيمة Result
وهي مُعدّد enumeration وغالبًا ما يُختصر بكتابة enum؛ وهو نوع يُمكن أن يأخذ عدّة حالات ونسمّي كل حالة ممكنة له بمتغاير variant.
سنغطّي المعددات بتفصيل أكبر لاحقًا، إلا أن الهدف من أنواع Result
هو لترميز معلومات التعامل مع الأخطاء.
متغايرات Result
هي Ok
و Err
؛ إذ يشير مغاير Ok
إلى نجاح العملية ويحتوي بداخله على قيمة النجاح المولّدة؛ بينما يشير المتغاير Err
إلى فشل العملية ويحتوي بداخله على معلومات حول سبب أو كيفية فشلها.
لقيم النوع Result
توابع معرفة لهم مثل أي قيم من نوع أخر، وتحتوي نسخةُ من النوع Result
على التابع expect
الذي يمكنك استدعاءه؛ فإذا كانت نسخة Result
هذه لها قيمة Err
فهذا يعني أن التابع expect
سيتسبب بتوقف البرنامج وعرض الرسالة التي مرّرتها وسيطًا إلى التابع expect
؛ وإذا أعاد التابع read_line
قيمة Err
، فهذا يعني أن الخطأ الناجم مرتبط بنظام التشغيل؛ وإذا كانت نسخة Result
تحتوي على القيمة Ok
، فسيأخذ التابع expect
القيمة المُعادة التي تخزنها Ok
ويُعيد القيمة إليك فقط كي تستخدمها، وتمثل القيمة في هذه الحالة عدد البايتات التي أدخلها المستخدم.
إذا لم تستدعي التابع expect
، سيُصرّف البرنامج بصورةٍ طبيعية، ولكنك ستحصل على التحذير التالي:
$ cargo build Compiling guessing_game v0.1.0 (file:///projects/guessing_game) warning: unused `Result` that must be used --> src/main.rs:10:5 | 10 | io::stdin().read_line(&mut guess); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `#[warn(unused_must_use)]` on by default = note: this `Result` may be an `Err` variant, which should be handled warning: `guessing_game` (bin "guessing_game") generated 1 warning Finished dev [unoptimized + debuginfo] target(s) in 0.59s
يحذرك رست هنا أنك لم تستخدم قيمة Result
المُعادة من التابع read_line
، مما يعني أن البرنامج لن يستطيع التعامل مع الأخطاء ممكنة الحدوث.
الطريقة الصحيحة في تجنُّب التحذيرات هي بتطبيق طريقة معينة للتعامل مع أخطاء، إلا أننا نريد من البرنامج أن يتوقف في حالتنا هذه، لذا فيمكننا استخدام expect
. ستتعلم ما يخص التعافي من الأخطاء (متابعة عمل البرنامج بعد ارتكاب الأخطاء) لاحقًا.
طباعة القيم باستخدام مواضع println! المؤقتة
هناك سطر واحد متبقي لمناقشته -بتجاهل الأقواس المعقوصة- ألا وهو:
println!("You guessed: {guess}");
يطبع هذا السطر السلسلة النصية التي تحتوي دخل المستخدم، وتمثل مجموعة الأقواس المعقوصة {}
مواضع مؤقتة placeholders. فكّر بالأمر وكأن {}
كماشة سلطعون تُمسك القيمة في مكانها، ويمكنك طباعة أكثر من قيمة واحدة باستخدام الأقواس المعقوصة، إذ يدل أول قوسين على أول قيمة موجودة في لائحة بعد السلسلة النصية المُنسقّة، ويدل ثاني قوسين على القيمة الثاني في اللائحة وهلم جرًّا. إذا أردنا طباعة عدة قيم باستخدام استدعاء واحد للماكرو println!
فسيبدو على النحو التالي:
let x = 5; let y = 10; println!("x = {} and y = {}", x, y);
سنحصل بعد تنفيذ الشيفرة السابقة على الخرج x = 5 and y = 10
.
التأكد من عمل الجزء الأول
دعنا نتأكد من عمل الجزء الأول من لعبة التخمين، لذلك شغّل الشيفرة البرمجية باستخدام الأمر cargo run
:
$ cargo run Compiling guessing_game v0.1.0 (file:///projects/guessing_game) Finished dev [unoptimized + debuginfo] target(s) in 6.44s Running `target/debug/guessing_game` Guess the number! Please input your guess. 6 You guessed: 6
وبهذا تكون قد أنجزت الجزء الأول من اللعبة، والبرنامج الآن قادر على استقبال الدخل من لوحة المفاتيح وطباعته.
توليد الرقم السري
الآن، علينا أن نولد الرقم السري الذي سيخمنه المستخدم، إذ يجب أن يكون هذا الرفم مختلفًا في كل مرة حتى تكون اللعبة أكثر متعةً للعب في كل مرة تحاول التخمين. سنستخدم رقمًا عشوائيًا بين 1 و100 حتى لا تكون اللعبة صعبةً جدًا. لا تتضمّن المكتبة القياسية الخاصة برست على مولّد عشوائي للأرقام، إلا أن فريق تطوير رست يقدم صندوق rand
بهذه الوظيفة.
استخدام صندوق ما للحصول على إمكانيات أكبر
تذكر أن الصندوق هو مجموعة من ملفات رست المصدرية، إذ يمثّل المشروع الذي نبنيه الآن صندوقًا ثنائيًا binary crate أي أنه ملف تنفيذي، بينما يمثل صندوق rand
صندوق مكتبة library crate، أي أنه يحتوي شيفرة مصدرية مكتوبة لتُستخدم في برامج أخرى ولا يمكن تشغيلها بصورةٍ مستقلة.
تبرز أداة كارجو عند تنسيقها للصناديق الخارجية. قبل كتابة الشيفرة البرمجية التي تستخدم rand
، علينا تعديل الملف "Cargo.toml" لتضمين صندوق rand
مثل اعتمادية. افتح الملف وأضِف السطر التالي إلى نهاية الملف أسفل ترويسة القسم [dependencies]
التي أنشأه لك كارجو مسبقًا، وتأكد من تحديد rand
بدقة باستخدام رقم الإصدار وإلا فإن الشيفرة البرمجية الموجودة في المقال قد لا تعمل.
اسم الملف: Cargo.toml
rand = "0.8.3"
كُل ما يتبع ترويسة القسم في ملف "Cargo.toml" هو جزءٌ من قسم ما يستمر حتى بداية القسم الآخر، وفي القسم [dependencies]
أنت تُعلم كارجو بالصناديق الخارجية التي يعتمد مشروعك عليها وأي إصدار منها يتطلّب، ونحدّد في حالتنا هذه الصندوق rand
ذو الإصدار "0.8.3"، ويفهم كارجو الإدارة الدلالية لنسخ البرمجيات Semantic Versioning -أو اختصارًا SemVer- وهي صيغة قياسية لكتابة أرقام الإصدارات، وفي الحقيقة فإن الرقم "0.8.3" هو اختصارٌ للرقم "^0.8.3"، وهو يعني أن أي إصدار مسموح هو "0.8.3" على الأقل و"0.9.0" على الأكثر.
يضع كارجو في الحسبان أن هذه الإصدارات تحتوي على واجهات برمجية عامة public APIs متوافقة مع الإصدار "0.8.3" ويضمن ذلك التحديد أنك ستحصل على آخر الإصدارات المتوافقة مع الشيفرة البرمجية في هذا المقال، إذ من غير المضمون أن تكون الإصدارات المساوية إلى "0.9.0" أو أعلى تحتوي على ذات الواجهة البرمجية التي نتبعها في الأمثلة هنا.
الآن ومن دون تغيير في الشيفرة البرمجية، دعنا نبني المشروع كما هو موضح في الشيفرة 2-2.
$ cargo build Updating crates.io index Downloaded rand v0.8.3 Downloaded libc v0.2.86 Downloaded getrandom v0.2.2 Downloaded cfg-if v1.0.0 Downloaded ppv-lite86 v0.2.10 Downloaded rand_chacha v0.3.0 Downloaded rand_core v0.6.2 Compiling rand_core v0.6.2 Compiling libc v0.2.86 Compiling getrandom v0.2.2 Compiling cfg-if v1.0.0 Compiling ppv-lite86 v0.2.10 Compiling rand_chacha v0.3.0 Compiling rand v0.8.3 Compiling guessing_game v0.1.0 (file:///projects/guessing_game) Finished dev [unoptimized + debuginfo] target(s) in 2.53s
[شيفرة 2-2: الخرج الناتج من تنفيذ الأمر cargo build
بعد إضافة صندوق rand مثل اعتمادية]
قد تجد اختلافًا في أرقام الإصدارات (إلا أنها ستكون متوافقة مع الشيفرة البرمجية والشكر إلى SemVer) وسطورًا مختلفة (بحسب نظام التشغيل الذي تستخدمه) وقد تكون السطور مكتوبةً بترتيب مختلف.
يبحث كارجو عن آخر الإصدارات التي تحتاجها اعتمادية خارجية عند تضمينها وذلك من المسجل registry وهي نسخة من البيانات من Crates.io، وهو موقع ينشر فيه الناس مشاريع رست مفتوحة المصدر حتى يتسنى للآخرين استخدامها.
يتفقد كارجو قسم [dependencies]
بعد تحديث المسجل ويحمّل أي صندوق موجود لم يُحمّل بعد. في حالتنا هذه وعلى الرغم من أننا أضفنا rand
فقط مثل اعتمادية، فقد أضاف كارجو أيضًا صناديق أخرى يعتمد rand
عليها حتى يعمل، وبعد تحميل الصناديق يُصرّفها رست ويصرّف المشروع باستخدام الاعتماديات المتاحة.
إذا نفذت الأمر cargo build
مجددًا دون أي تغيير فلن تحصل على أي خرج إضافي عن السطر Finished
، إذ يعرف كارجو أنه حمّل وصرّف الاعتماديات وأنك لم تغيّر أي شيء بخصوصهم في ملف "Cargo.toml"، كما يعرف كارجو أنك لم تغيّر أي شيء على شيفرتك البرمجية ولهذا فهو لا يُعيد تصريفها أيضًا، وفي هذه الحالة لا يوجد أي شيء ليفعله ويغادر مباشرةً.
إذا فتحت الملف "src/main.rs" وعدّلت تعديلًا بسيطًا ومن ثمّ حفظته وحاولت إعادة بناء المشروع، فستجد السطرين التاليين فقط في الخرج:
$ cargo build Compiling guessing_game v0.1.0 (file:///projects/guessing_game) Finished dev [unoptimized + debuginfo] target(s) in 2.53 secs
توضح السطور السابقة أن كارجو حدّث وبنى التغييرات الطفيفة إلى الملف "src/main.rs"، ويعلم كارجو أنه يستطيع إعادة استخدام الاعتماديات التي حمّلها سابقًا وصرّفها بما أنك لم تعدل عليها.
التأكد من أن المشاريع يمكن إعادة إنتاجها باستخدام ملف Cargo.lock
لدى كارجو آلية تتحقق من أنك تستطيع إعادة بناء الأداة كل مرة تبني أنت أو شخص آخر شيفرتك البرمجية، يستخدم كارجو فقط إصدارات الاعتماديات التي حددتها إلا إذا حددت عكس ذلك. على سبيل المثال، لنقل أن إصدار 0.8.4 من صندوق rand
سيُطلق الأسبوع القادم، ويتضمن هذا الإصدار تصحيحًا مهمًا لمشكلة ما إلا أنه يحتوي أيضًا على تراجع regression، وسيتسبب هذا بتعطل شيفرتك البرمجية؛ تُنشئ رست في هذه الحالة ملفًا يدعى "Cargo.lock" عند أول تنفيذ للأمر cargo build
ويقع هذا الملف في مجلد "guessing_game".
يوجِد كارجو جميع إصدارات الاعتماديات التي تلائم مشروعك عندما تبنيه للمرة الأولى، ومن ثم يكتب الإصدارات إلى ملف "Cargo.lock"، وبالتالي سيجد كارجو عندما تبني مشروعك في المستقبل أن الملف "Cargo.lock" موجود وسيستخدم عندها الإصدارات المحددة في ذلك الملف بدلًا من إيجاد الإصدارات المناسبة مجددًا، ويسمح لك هذا بالحصول على نسخة من المشروع قابلة لإعادة الإنتاج تلقائيًا، وبكلمات أخرى، سيظلّ مشروعك معتمدًا على الإصدار "0.8.3" حتى تقرّر التحديث إلى إصدار آخر بصورةٍ صريحة، ويعود الشكر إلى ملف "Cargo.lock" في ذلك. بما أن ملف "Cargo.lock" مهم للحصول على نسخ قابلة لإعادة الإنتاج، فمن الشائع أن يُضاف إلى نظام التحكم بالإصدارات version control مع باقي الشيفرة المصدرية في مشروعك.
تحديث صندوق للحصول على إصدار جديد
يقدم لك كارجو إمكانية تحديث صندوق ما باستخدام الأمر update
، الذي سيتجاهل بدوره الملف "Cargo.lock" وسيبحث عن آخر الإصدارات التي تلائم متطلباتك في ملف "Cargo.toml"، إذ يكتب كارجو هذه الإصدارات إلى "Cargo.lock"، وإلا فسيبحث كارجو افتراضيًا عن إصدارات أحدث من "0.8.3" وأقدم من "0.9.0". إذا كان للصندوق rand
إصداران جديدان هما "0.8.4" و"0.9.0" فستجد ما يلي عند تشغيل cargo update
:
$ cargo update Updating crates.io index Updating rand v0.8.3 -> v0.8.4
يتجاهل كارجو الإصدار "0.9.0"، وستلاحظ أيضًا بحلول هذه النقطة أن ملف "Cargo.lock" يُشير إلى أن الإصدار الحالي من صندوق rand
هو "0.8.4"؛ ولاستخدام الإصدار "0.9.0" من rand
أو أي إصدار آخر من السلسلة "x.0.9"K عليك تحديث ملف "Cargo.toml" ليبدو على النحو التالي:
[dependencies] rand = "0.9.0"
سيحدّث كارجو في المرة القادمة التي تنفذ فيها الأمر cargo build
مسجّل الصناديق المتاحة ويُعيد تقييم متطلبات rand
حسب الإصدار الجديد الذي حدّدته.
هناك الكثير من الأشياء التي يمكننا الحديث عنها بخصوص كارجو ونظامه، وهذا ما سنفعله لاحقًا، إلا أن ما ذكرناه الآن كافي مبدئيًا. يجعل كارجو عملية إعادة استخدام المكتبات عملية أكثر سهولة، ويمكّن مستخدمي لغة رست من كتابة مشاريع صغيرة تعتمد على عدد من الحزم.
توليد الرقم العشوائي
دعنا نبدأ باستخدام rand
لتوليد الرقم العشوائي، إذ تكمن خطوتنا التالية في التعديل على محتويات الملف "src/main.rs" كما هو موضح في الشيفرة 2-3.
use std::io; use rand::Rng; fn main() { println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1..=100); println!("The secret number is: {secret_number}"); println!("Please input your guess."); let mut guess = String::new(); io::stdin() .read_line(&mut guess) .expect("Failed to read line"); println!("You guessed: {guess}"); }
[شيفرة 2-3: إضافة شيفرة برمجية لتوليد الرقم العشوائي]
نُضيف أولًا السطر use rand::Rng
، إذ تُعرّف السمة Rng
التوابع التي تستخدمها مولّدات الأرقام العشوائية، ويجب أن تكون هذه السمة ضمن النطاق حتى نستطيع استخدام هذه التوابع. سنناقش السمات بتفصيل أكبر لاحقًا.
ثم نُضيف سطرين في وسط البرنامج، إذ نستدعي في السطر الأول الدالة rand::thread_rng
التي تُعطينا مولّد رقم عشوائي معيّن سنستخدمه وهو مولد محلي لخيط التنفيذ الحالي ويُبذر seeded بواسطة نظام التشغيل، ونستدعي بعدها التابع gen_range
على مولد الأرقام العشوائية، التابع السابق معرّف بالسمة Rng
التي أضفناها إلى النطاق باستخدام التعليمة use rand::Rng
. يأخذ التابع gen_rang
تعبير مجال range expression مثل وسيط، ويولّد رقمًا ينتمي إلى ذلك المجال، إذ نستخدم تعبير المجال من النوع ذو التنسيق start..=end
، ويتضمن المجال الحد الأعلى والأدنى داخله، لذا بكتابتنا للمجال "1..=100" فنحن نحدّد الأعداد بين 1 و100.
ملاحظة: لن تعرف أي السمات وأي التوابع والدوال التي يجب أن تستدعيها من الصندوق من تلقاء نفسك، لذا يتضمن كل صندوق توثيق مرفقًا بتوجيهات لكيفية استخدام الصندوق. ميزة أخرى لطيفة من كارجو هي أن تنفيذ الأمر cargo doc --open
سيتسبب ببناء التوثيق المزوّد بواسطة جميع الاعتماديات المحلية وفتحها في متصفحك، وإن كنت مهتمًا على سبيل المثال بالاستخدامات الأخرى الموجودة في الصندوق rand
فكل ما عليك فعله هو تنفيذ الأمر cargo doc --open
والنقر على rand
في الشريط الجانبي على الجانب الأيسر.
يطبع السطر الجديد الثاني الرقم السري، وهذا مفيد بينما تُطوّر البرنامج حتى تكون قادرًا على تجربته، إلا أننا سنحذفه من الإصدار الأخير في نهاية المطاف، فاللعبة عديمة الفائدة إذا كانت تطبع الإجابة فور تشغيلها.
جرّب تنفيذ البرنامج عدة مرات:
$ cargo run Compiling guessing_game v0.1.0 (file:///projects/guessing_game) Finished dev [unoptimized + debuginfo] target(s) in 2.53s Running `target/debug/guessing_game` Guess the number! The secret number is: 7 Please input your guess. 4 You guessed: 4 $ cargo run Finished dev [unoptimized + debuginfo] target(s) in 0.02s Running `target/debug/guessing_game` Guess the number! The secret number is: 83 Please input your guess. 5 You guessed: 5
يجب أن تحصل على رقم مختلف في كل مرة ويجب أن تكون الأرقام بين 1 و100، وإذا حدث ذلك فأحسنت.
مقارنة التخمين مع الرقم السري
يمكننا الآن مقارنة التخمين الذي أدخله المستخدم مع الرقم السري العشوائي، وتوضّح الشيفرة 2-4 هذه الخطوة، لاحظ أن الشيفرة البرمجية لن تُصرّف بنجاح بعد كما سنوضح لاحقًا.
اسم الملف: src/main.rs
use rand::Rng; use std::cmp::Ordering; use std::io; fn main() { // --snip-- println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1..=100); println!("The secret number is: {secret_number}"); println!("Please input your guess."); let mut guess = String::new(); io::stdin() .read_line(&mut guess) .expect("Failed to read line"); println!("You guessed: {guess}"); match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => println!("You win!"), } }
[شيفرة 2-4: التعامل مع الحالات المُمكنة ضمن عملية المقارنة]
نُضيف أولًا تعليمة use
أخرى تقدّم لنا نوعًا جديدًا يدعى std::cmp::Ordering
إلى النطاق من المكتبة القياسية، والنوع Ordering
هو مُعدّد enum آخر يحتوي على المتغايرات Less
و Greater
و Equal
، وهي النتائج الثلاث الممكنة عندما تُقارن ما بين قيمتين.
نُضيف بعدها خمسة أسطر في النهاية، وتستخدم هذه الأسطر بدورها النوع Ordering
، ويُقارن التابع cmp
بين قيمتين ويُمكن استدعاؤه على أي شيء يمكن مقارنته، ويتطلب الأمر استخدام المرجع للشيء الذي تريد مقارنته، وفي حالتنا هذه فهو يقارن guess
مع secret_number
، ثم يُعيد متغايرًا من متغايرات المعدّد Ordering
الذي أضفناه سابقًا إلى النطاق باستخدام تعليمة use
، ونستخدم هنا تعبير match
لتحديد ما الذي سنفعله لاحقًا بناءً على متغاير Ordering
المُعاد من استدعاء cmp
باستخدام القيمتين guess
و secret_number
.
يتألف تعبير match
من أذرع arms، ويتألف كل ذراع من نمط يُستخدم في عملية المقارنة والشيفرة البرمجية التي يجب أن تعمل في حال كانت القيمة المُعطاة إلى match
توافق نمط الذراع. تأخذ رست القيمة المُعطاة إلى match
وتنظر إلى كلّ نمط ذراع. تُعدّ الأذرع وبنية match
من أبرز مزايا رست، إذ تسمح لك بالتعبير عن عدّة حالات قد تحدث ضمن شيفرتك البرمجية وأن تتأكد من معالجتها جميعًا، وسنغطّي هذه المزايا بتعمُّق أكبر لاحقًا.
دعنا نوضح مثالًا عن تعبير match
نستخدمه هنا؛ لنقُل أن المستخدم قد خمّن القيمة 50 وأن الرقم العشوائي السري المولّد كان 38، تُقارن شيفرتنا البرمجية القيمة 50 إلى 38 ويُعيد التابع cmp
في هذه الحالة القيمة Ordering::Greater
لأن 50 أكبر من 38، ويتلقّى التعبير match
القيمة Ordering::Greater
ويبدأ بتفقد كل نمط ذراع، إذ يُنظر إلى نمط الذراع الأولى وهو Ordering::Less
وهي قيمةٌ لا توافق القيمة Ordering::Greater
وبالتالي يجري تجاهل الشيفرة البرمجية ضمن الذراع وينتقل إلى نمط الذراع الأخرى وهو Ordering::Greater
الذي يُطابق !Ordering::Greater
، وبالتالي تُنفّذ الشيفرة البرمجية الموجود ضمن الذراع ويُطبع النص "Too big!" إلى الشاشة. ينتهي التعبير match
بعد أول مطابقة ناجحة، لذا لن تجري المطابقة مع نمط الذراع الثالثة في هذه الحالة.
إلا أن الشيفرة 2-4 لن تُصرّف، دعنا نجرّب ذلك:
$ cargo build Compiling libc v0.2.86 Compiling getrandom v0.2.2 Compiling cfg-if v1.0.0 Compiling ppv-lite86 v0.2.10 Compiling rand_core v0.6.2 Compiling rand_chacha v0.3.0 Compiling rand v0.8.3 Compiling guessing_game v0.1.0 (file:///projects/guessing_game) error[E0308]: mismatched types --> src/main.rs:22:21 | 22 | match guess.cmp(&secret_number) { | ^^^^^^^^^^^^^^ expected struct `String`, found integer | = note: expected reference `&String` found reference `&{integer}` For more information about this error, try `rustc --explain E0308`. error: could not compile `guessing_game` due to previous error
تتلخّص المشكلة الأساسية بوجود أنواع غير متوافقة mismatched types. لدى رست نظام نوع ساكن static type قوي، إلا أنها تحتوي أيضًا على واجهة نوع type interface، وبالتالي استنتجت رست عند كتابتنا للتعليمة let mut guess = String::new()
بأن guess
يجب أن تكون String
ولم تُجبرنا على كتابة النوع، بينما secret_number
على الجانب الآخر هو من نوع عددي وهناك عدد من أنواع رست الرقمية التي يمكن أن تحتوي على القيم بين 1 و100، مثل i32
وهو عدد بطول 32 بت، و u32
وهو عدد عديم الإشارة unsigned بطول 32 بت، وi64
وهو عدد بطول 64 بت، إضافةً إلى أنواع أخرى، ويستخدم رست النوع i32
افتراضيًا إن لم يُحدد النوع وهو نوع secret_number
في هذه الحالة، والسبب في حدوث المشكلة هو عدم قدرة رست على المقارنة بين نوع عددي وسلسلة نصية.
إذًا، علينا أن نحوّل النوع String
الذي يقرأه البرنامج في الدخل إلى نوع عددي، وذلك كي يتسنّى لنا مقارنته مقارنةً عدديّةً مع الرقم السري، ونُنجز ذلك عن طريق إضافة السطر الجديد التالي إلى متن الدالة main
:
اسم الملف: src/main.rs
use rand::Rng; use std::cmp::Ordering; use std::io; fn main() { println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1..=100); println!("The secret number is: {secret_number}"); println!("Please input your guess."); // --snip-- let mut guess = String::new(); io::stdin() .read_line(&mut guess) .expect("Failed to read line"); let guess: u32 = guess.trim().parse().expect("Please type a number!"); println!("You guessed: {guess}"); match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => println!("You win!"), } }
السطر الجديد هو:
let guess: u32 = guess.trim().parse().expect("Please type a number!");
نُنشئ متغيرًا باسم "guess"، ولكن مهلًا ألا يوجد متغير باسم "guess" في برنامجنا مسبقًا؟ نعم، ولكن رست تسمح لنا بتظليل shadow القيمة السابقة للمتغير guess
بقيمة أخرى جديدة، ويسمح لنا التظليل بإعادة استخدام اسم المتحول guess
بدلًا من إجبارنا على إنشاء متغير جديد، بحيث يصبح لدينا مثلًا guess_str
و guess
، وسنغطّي هذا الأمر بتفصيلٍ أكبر لاحقًا، ويكفي الآن أن تعرف بوجود هذه الميزة وأن استخدامها شائع عندما نريد تحويل قيمة من نوع إلى آخر.
نُسند المتغير الجديد إلى التعبير guess.trim().parse()
، إذ تشير guess
ضمن التعبير إلى المتغير guess
الأصلي الذي يحتوي على الدخل (سلسلة نصية string). يحذف التابع trim
عند استخدامه ضمن نسخة من النوع String
أي مسافات فارغة whitespaces في بداية ونهاية السلسلة النصية، وهو أمر لازم الحدوث قبل أن نحوّل السلسلة النصية إلى النوع u32
الذي يمكن أن يحتوي فقط على قيمة عددية، وذلك لأن المستخدم يضغط على زرّ الإدخال enter لإنهاء عمل التابع read_line
بعد إدخال تخمينه مما يُضيف محرف سطر جديد إلى السلسلة النصية؛ فعلى سبيل المثال، إذا أدخل المستخدم 5 وضغط على زر الإدخال، فستأخذ السلسلة النصية guess
القيمة "5\n"، إذ يمثل المحرف "\n" محرف سطر جديد (يتسبب الضغط على زر الإدخال في أنظمة ويندوز برجوع السطر وإضافة سطر جديد "\r\n") ويُزيل التابع trim
المحرف "\n" أو "\r\n" ونحصل بالتالي على "5" فقط.
يحول التابع parse
السلاسل النصية إلى نوع آخر، ونستخدمه هنا لتحويل السلسلة النصية إلى عدد، وعلينا إخبار رست بتحديد النوع الذي نريد التحويل إليه باستخدام let guess: u32
، إذ تُشير النقطتان ":" الموجودتان بعد guess
إلى أننا سنحدد نوع المتغير بعدها. تحتوي رست على عدد من الأنواع العددية المُضمّنة built-in منها u32
الذي استخدمناه هنا وهو نوع عدد صحيح عديم الإشارة بطول 32-بت وهو خيار افتراضي جيّد للقيم الموجبة الصغيرة، وستتعلّم لاحقًا عن الأنواع العددية الأخرى. إضافةً إلى ما سبق، تعني u32
في مثالنا والمقارنة مع secret_number
أن رست سيستنتج أن secret_number
يجب أن تكون من النوع u32
أيضًا، لذا أصبحت المقارنة الآن بين قيمتين من النوع ذاته.
يعمل التابع parse
فقط على المحارف التي يمكن أن تُحوَّل منطقيًا إلى أعداد، لذلك من الشائع أن يتسبّب بأخطاء؛ فعلى سبيل المثال لن يستطيع التابع التحويل السلسلة النصية إلى نوع عددي إذا كانت تحتوي على القيمة "A?%" ولهذا السبب فإن التابع parse
يُعيد أيضًا النوع Result
بصورةٍ مماثلة للتابع read_line
، الذي ناقشناه سابقًا في فقرة "التعامل مع الأخطاء الممكنة باستخدام النوع Result
"، وسنتعامل مع النوع Result
هذا بطريقة مماثلة باستخدام تابع expect
مجددًا. إذا أعاد التابع parse
متغاير Result
المتمثل بالقيمة Err
فهذا يعني أنه لم يستطع التحويل إلى نوع عددي من السلسلة النصية، وفي هذه الحالة، سيوقف استدعاء expect
اللعبة وستُطبع الرسالة التي نمررها له، بينما يُعيد متغاير Result
ذو القيمة Ok
إذا استطاع تحويل القيمة بنجاح من نوع سلسلة نصية إلى نوع عددي، وتُعيد عندها expect
العدد الذي نريده من قيمة Ok
.
دعنا نشغّل البرنامج الآن.
$ cargo run Compiling guessing_game v0.1.0 (file:///projects/guessing_game) Finished dev [unoptimized + debuginfo] target(s) in 0.43s Running `target/debug/guessing_game` Guess the number! The secret number is: 58 Please input your guess. 76 You guessed: 76 Too big!
رائع، فعلى الرغم من أننا أضفنا مسافات فارغة قبل التخمين، إلا أن البرنامج توصّل إلى أن تخمين المستخدم هو 67. شغّل البرنامج عدّة مرات أخرى لتتأكد من السلوك المختلف لحالات مختلفة من الإدخال: خمّن العدد بصورةٍ صحيحة، خمّن عددًا أكبر من الإجابة، خمّن عددًا أصغر من الإجابة.
تعمل اللعبة لدينا الآن جيدًا، إلا أن المستخدم يمكنه التخمين مرةً واحدةً فقط، دعنا نغيّر من ذلك بإضافة حلقة تكرارية loop.
السماح بعدة تخمينات باستخدام الحلقات التكرارية
تُنشئ الكلمة المفتاحية loop
حلقةً تكراريةً لا نهائية، وسنستخدم الحلقة هنا بهدف منح المستخدم فرصًا أكبر في تخمين العدد:
اسم الملف: src/main.rs
use rand::Rng; use std::cmp::Ordering; use std::io; fn main() { println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1..=100); // --snip-- println!("The secret number is: {secret_number}"); loop { println!("Please input your guess."); // --snip-- let mut guess = String::new(); io::stdin() .read_line(&mut guess) .expect("Failed to read line"); let guess: u32 = guess.trim().parse().expect("Please type a number!"); println!("You guessed: {guess}"); match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => println!("You win!"), } } }
نقلنا محتوى البرنامج من تلقي الدخل guess إلى ما بعده لداخل الحلقة. تأكد من محاذاة السطور الموجودة داخل الحلقة التكرارية بمقدار أربع مسافات فارغة، وشغّل البرنامج مجددًا. سيسألك البرنامج الآن عن تخمين جديد إلى ما لا نهاية وهذه مشكلةٌ جديدة، إذ لن يستطيع المستخدم الخروج من البرنامج في هذه الحالة.
يمكن للمستخدم إيقاف البرنامج قسريًا عن طريق استخدام اختصار لوحة المفاتيح "ctrl-c"، إلا أن هناك طريقة أخرى للهروب من هذا البرنامج الذي لا يشبع، إذ يمكن للمستخدم أن يُدخل قيمة غير عددية كما ذكرنا في القسم "مقارنة التخمين إلى الرقم السري" الذي يناقش استخدام parse
ويتسبب ذلك بتوقف البرنامج، ويمكننا الاستفادة من ذلك الأمر بالسماح لمستخدمنا بمغادرة البرنامج كما هو موضح هنا:
$ cargo run Compiling guessing_game v0.1.0 (file:///projects/guessing_game) Finished dev [unoptimized + debuginfo] target(s) in 1.50s Running `target/debug/guessing_game` Guess the number! The secret number is: 59 Please input your guess. 45 You guessed: 45 Too small! Please input your guess. 60 You guessed: 60 Too big! Please input your guess. 59 You guessed: 59 You win! Please input your guess. quit thread 'main' panicked at 'Please type a number!: ParseIntError { kind: InvalidDigit }', src/main.rs:28:47 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
نستطيع الآن مغادرة اللعبة بكتابة quit
، إلا أنك ستلاحظ أن إدخال أي قيمة غير عددية سيتسبب بذلك أيضًا، ولكن مشاكلنا لم تنتهي بعد، فما زلنا نريد أن نغادر اللعبة بعد أن نحصل على التخمين الصحيح.
مغادرة اللعبة بعد إدخال التخمين الصحيح
دعنا نبرمج اللعبة بحيث نُغادر منها عند فوز المستخدم بإضافة تعليمة break
:
اسم الملف: src/main.rs
use rand::Rng; use std::cmp::Ordering; use std::io; fn main() { println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1..=100); println!("The secret number is: {secret_number}"); loop { println!("Please input your guess."); let mut guess = String::new(); io::stdin() .read_line(&mut guess) .expect("Failed to read line"); let guess: u32 = guess.trim().parse().expect("Please type a number!"); println!("You guessed: {guess}"); // --snip-- match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => { println!("You win!"); break; } } } }
عند إضافة السطر break
بعد "You win!"، يخرج البرنامج من الحلقة عندما يكون تخمين المستخدم مساويًا إلى الرقم السري، ويعني الخروج من الحلقة أيضًا الخروج من البرنامج لأن الحلقة هي آخر جزء من الدالة main
.
التعامل مع الدخل غير الصالح
دعنا نجعل البرنامج يتجاهل دخل المستخدم عندما يكون ذو قيمة غير عددية بدلًا من إيقافه لتحسين اللعبة أكثر، وذلك ليتسنّى للمستخدم إعادة إدخال التخمين بصورةٍ صحيحة. يمكننا تحقيق ما سبق عن طريق تغيير السطر الذي يحتوي على تحويل guess
من String
إلى u32
كما توضح الشيفرة 2-5.
اسم الملف: src/main.rs
use rand::Rng; use std::cmp::Ordering; use std::io; fn main() { println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1..=100); println!("The secret number is: {secret_number}"); loop { println!("Please input your guess."); let mut guess = String::new(); // --snip-- io::stdin() .read_line(&mut guess) .expect("Failed to read line"); let guess: u32 = match guess.trim().parse() { Ok(num) => num, Err(_) => continue, }; println!("You guessed: {guess}"); // --snip-- match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => { println!("You win!"); break; } } } }
[شيفرة 2-5: تجاهل تخمين غير عددي وسؤال المستخدم عن تخمين آخر بدلًا من إيقاف البرنامج]
بدّلنا استدعاء التابع expect
بتعبير match
لتفادي إيقاف البرنامج والتعامل مع الخطأ. تذكر أن parse
تُعيد قيمةً من نوع Result
ويمثّل Result
معدّدًا يحتوي على المغايرين Ok
و Err
. نستخدم هنا تعبير match
بصورةٍ مماثلة لما فعلناه عند استخدام نتيجة Ordering
في تابع cmp
.
إذا نجح التابع parse
بتحويل السلسلة النصية إلى عدد، فسيعيد القيمة Ok
التي تحتوي على العدد الناتج، وستطابق قيمة Ok
نمط الذراع الأول وبذلك سيعيد تعبير match
قيمة num
التي أنتجها التابع parse
ووضعها داخل قيمة Ok
، وسينتهي المطاف بهذا الرقم حيث نريده في متغير guess
الجديد الذي أنشأناه.
إذا لم يكن التابع parse
قادرًا على تحويل السلسلة النصية إلى عدد، فسيعيد قيمةً من النوع Err
التي تحتوي بدورها على معلومات حول الخطأ، لا تُطابق قيمة Err
نمط Ok(num)
في ذراع match
الأولى إلا أنها تطابق النمط Err(_)
في الذراع الثانية، وترمز الشرطة السفلية _
إلى الحصول على جميع القيم الممكنة، وفي مثالنا هذا فنحن نقول أننا نريد أن نطابق جميع قيم Err
الممكنة بغض النظر عن المعلومات الموجودة داخلها، وبالتالي سينفذ البرنامج الذراع الثانية التي تتضمن على continue
التي تخبر البرنامج بالذهاب إلى الدورة الثانية من الحلقة loop
وأن تسأل المستخدم عن تخمينٍ آخر، لذا أصبح برنامجنا يتجاهل جميع أخطاء parse
الممكنة بنجاح.
يجب أن تعمل جميع أجزاء البرنامج كما هو متوقّع، لنجرّبه:
$ cargo run Compiling guessing_game v0.1.0 (file:///projects/guessing_game) Finished dev [unoptimized + debuginfo] target(s) in 4.45s Running `target/debug/guessing_game` Guess the number! The secret number is: 61 Please input your guess. 10 You guessed: 10 Too small! Please input your guess. 99 You guessed: 99 Too big! Please input your guess. foo Please input your guess. 61 You guessed: 61 You win!
عظيم، استطعنا إنهاء كامل لعبة التخمين عن طريق تعديل بسيط، إلا أنه يجب أن تتذكر أن برنامجنا ما زال يطبع المرقم السري، وذلك ساعدنا جدًا خلال تجربتنا للبرنامج وفحصه إلا أنه يُفسد لعبتنا، لذا لنحذف السطر println!
الذي يطبع الرقم السري على الشاشة. توضح الشيفرة 2-6 محتوى البرنامج النهائي.
اسم الملف: src/main.rs
use rand::Rng; use std::cmp::Ordering; use std::io; fn main() { println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1..=100); loop { println!("Please input your guess."); let mut guess = String::new(); io::stdin() .read_line(&mut guess) .expect("Failed to read line"); let guess: u32 = match guess.trim().parse() { Ok(num) => num, Err(_) => continue, }; println!("You guessed: {guess}"); match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => { println!("You win!"); break; } } } }
[شيفرة 2-6: الشيفرة البرمجية للعبة التخمين كاملةً]
ملخص
أنهينا بالوصول إلى هذه النقطة لعبة التخمين كاملةً، تهانينا.
كان هذا المشروع بمثابة تطبيق عملي وطريقة للتعرف على مفاهيم رست الجديدة، مثل let
و match
والدوال واستخدام الصناديق الخارجية وغيرها. ستتعلم المزيد عن هذه المفاهيم بالتفصيل فيما يتبع، إذ سنتكلم عن المفاهيم الموجودة في معظم لغات البرمجة، مثل المتغيرات وأنواع البيانات والدوال وسنستعرض كيفية استخدامها في لغة رست، ثم سنتوجه لمناقشة مفهوم الملكية ownership وهي ميزة تجعل من لغة رست مميّزة دونًا عن لغات البرمجة الأخرى، ومن ثمّ سنناقش صيغة syntax التابع والهياكل structs، ومن ثمّ سنشرح كيفية عمل المعدّدات enums.
ترجمة -وبتصرف- لفصل Programming a Guessing Game من كتاب The Rust Programming Language.
اقرأ أيضًا
- المقال التالي: المتغيرات والتعديل عليها في لغة رست
- المقال السابق: تعلم لغة رست Rust: البدايات
- تعلم البرمجة
- دليلك الشامل إلى لغات البرمجة
-
تعرف على أشهر لغات برمجة الألعاب
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.