مراجعة التعداد Option
جزء من قسم Object Oriented Programming في رحلة Rust على Coddy — الدرس 17 من 61.
لقد واجهت بالفعل Option<T> في لغة Rust—وهو الـ enum القياسي لتمثيل القيم التي قد تكون موجودة أو غير موجودة. الآن دعونا ننظر إليه من منظور البرمجة كائنية التوجه (OOP): الـ Option ليس مجرد enum، بل هو نوع يحتوي على دوال مدمجة قوية تتيح لك التعامل مع القيم التي قد تكون فارغة (nullable) بأمان وأناقة.
بدلاً من المطابقة في كل مرة، يمكنك استخدام الدوال (methods) مباشرة على قيم Option. تعيد الدالة unwrap_or القيمة الداخلية إذا كانت موجودة، أو قيمة افتراضية توفرها:
let maybe_name: Option<String> = Some(String::from("Alice"));
let name = maybe_name.unwrap_or(String::from("Guest"));
println!("{}", name); // Alice
let empty: Option<String> = None;
let fallback = empty.unwrap_or(String::from("Guest"));
println!("{}", fallback); // Guest
تقوم الطريقة map بتحويل القيمة الداخلية إذا كانت موجودة، مع ترك None دون تغيير. يتيح لك ذلك ربط العمليات ببعضها البعض دون الحاجة إلى التحقق باستمرار من None:
let maybe_num: Option<i32> = Some(5);
let doubled = maybe_num.map(|x| x * 2);
println!("{:?}", doubled); // Some(10)
let nothing: Option<i32> = None;
let still_nothing = nothing.map(|x| x * 2);
println!("{:?}", still_nothing); // None
تجسد هذه الطرق مبادئ البرمجة كائنية التوجه (OOP) - حيث يغلف النوع Option مفهوم "ربما قيمة" ويوفر واجهة نظيفة للتعامل معها. أنت تستدعي الطرق (methods) على الكائن بدلاً من كتابة منطق خارجي لفحصه.
التحدي
سهللنقم ببناء نظام لملفات تعريف المستخدمين يتعامل بسلاسة مع البيانات الاختيارية باستخدام طرق Option. قد يكون لدى المستخدمين اسم مستعار (nickname) محدد، أو قد لا يكون لديهم - وسيتعامل نظامك مع كلتا الحالتين بأناقة دون الحاجة إلى مطابقة صريحة (explicit matching).
ستقوم بإنشاء ملفين لتنظيم الكود الخاص بك:
profile.rs: قم بتعريف هيكلProfileعام مع حقلين:username(String) وnickname(Option<String>). قم بتنفيذ ما يلي:- منشئ
newيأخذ اسم مستخدم واسم مستعار اختياري. - طريقة
display_nameتعيد الاسم المستعار إذا كان موجوداً، أو اسم المستخدم كبديل - استخدمunwrap_orللتعامل مع هذا. - طريقة
formatted_nicknameتعيدOption<String>مع وضع الاسم المستعار بين أقواس (مثل"[CoolNick]") إذا كان موجوداً، أوNoneإذا لم يكن هناك اسم مستعار - استخدمmapلتحويل القيمة.
- منشئ
main.rs: قم باستيراد وحدة الملف الشخصي (profile module) وأنشئ ملفين شخصيين - أحدهما باسم مستعار والآخر بدونه. قم باستعراض كلتا الطريقتين على كل ملف شخصي لتوضيح كيف تتعامل طرقOptionمع القيم الموجودة والغائبة بشكل مختلف.
بالنسبة لطريقة formatted_nickname، استخدم تنسيق التصحيح ({:?}) عند الطباعة لأنها تعيد Option.
يجب أن يتبع مخرجك هذا التنسيق تماماً، مع إظهار كلا الملفين الشخصيين:
Display name: {name}
Formatted nickname: {option}
Display name: {name}
Formatted nickname: {option}على سبيل المثال، مع ملف شخصي لاسم المستخدم "alice" مع الاسم المستعار "Ace"، وآخر لاسم المستخدم "bob" بدون اسم مستعار، سيكون المخرج:
Display name: Ace
Formatted nickname: Some("[Ace]")
Display name: bob
Formatted nickname: Noneستتلقى ثلاثة مدخلات: اسم المستخدم الأول، والاسم المستعار للمستخدم الأول، واسم المستخدم الثاني (الذي ليس لديه اسم مستعار).
ورقة مرجعية
يوفر النوع Option<T> طرقاً للتعامل مع القيم الاختيارية دون الحاجة إلى المطابقة الصريحة (explicit matching).
تعيد الطريقة unwrap_or القيمة الداخلية إذا كانت موجودة، أو قيمة افتراضية إذا كانت القيمة هي None:
let maybe_name: Option<String> = Some(String::from("Alice"));
let name = maybe_name.unwrap_or(String::from("Guest"));
println!("{}", name); // Alice
let empty: Option<String> = None;
let fallback = empty.unwrap_or(String::from("Guest"));
println!("{}", fallback); // Guest
تقوم الطريقة map بتحويل القيمة الداخلية إذا كانت موجودة، مع ترك None دون تغيير:
let maybe_num: Option<i32> = Some(5);
let doubled = maybe_num.map(|x| x * 2);
println!("{:?}", doubled); // Some(10)
let nothing: Option<i32> = None;
let still_nothing = nothing.map(|x| x * 2);
println!("{:?}", still_nothing); // None
تسمح هذه الطرق بربط العمليات (chaining) على قيم Option دون الحاجة المستمرة للتحقق من وجود None.
جرّب بنفسك
mod profile;
use profile::Profile;
fn main() {
// قراءة المدخلات
let mut input1 = String::new();
std::io::stdin().read_line(&mut input1).expect("Failed to read line");
let username1 = input1.trim().to_string();
let mut input2 = String::new();
std::io::stdin().read_line(&mut input2).expect("Failed to read line");
let nickname1 = input2.trim().to_string();
let mut input3 = String::new();
std::io::stdin().read_line(&mut input3).expect("Failed to read line");
let username2 = input3.trim().to_string();
// TODO: إنشاء الملف الشخصي الأول باستخدام username1 و nickname1 (Some)
// TODO: إنشاء الملف الشخصي الثاني باستخدام username2 وبدون لقب (None)
// TODO: طباعة display_name و formatted_nickname للملف الشخصي الأول
// استخدم {:?} لـ formatted_nickname لأنه يعيد Option
// TODO: طباعة display_name و formatted_nickname للملف الشخصي الثاني
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1الأساليب والسلوك
مقدمة في كتل التنفيذمعامل Selfالأساليب القابلة للتعديلالدوال المرتبطةكتل تنفيذ متعددةتسلسل الأساليبمراجعة - إجراءات المستطيل4مشروع: حيوان أليف افتراضي
تعريف الحيوان الأليفإطعام الحيوان الأليف2التغليف والوحدات البرمجية
أساسيات الوحدات البرمجيةالكلمة المفتاحية Publicالحقول الخاصةدوال Gettersدوال Settersمراجعة - الخزانة الآمنة5الأنواع العامة (Generics)
الـ Structs العامةالـ Methods العامةأنواع عامة متعددةالـ Functions العامةمراجعة - Coordinate Point3التعدادات المتقدمة (Advanced Enums)
التعدادات مع البياناتالأساليب (Methods) في التعداداتمطابقة حالات البياناتمراجعة التعداد Optionملخص - التعداد Shape6تعريف الـ Traits
ما هي الـ Trait؟تنفيذ الـ Traitsالتنفيذات الافتراضيةتجاوز التنفيذات الافتراضيةالـ Traits مع المعاملاتملخص - مشغل الوسائط