مطابقة حالات البيانات
جزء من قسم Object Oriented Programming في رحلة Rust على Coddy — الدرس 16 من 61.
في الدرس السابق، استخدمت match داخل توابع الـ enum للتعامل مع المتغيرات المختلفة. الآن دعونا نركز على كيفية قيام match باستخراج البيانات المخزنة داخل تلك المتغيرات فعلياً—وهي تقنية تسمى destructuring.
عندما تقوم بالمطابقة على نوع من أنواع التعداد (enum variant) الذي يحتوي على بيانات، يمكنك ربط تلك البيانات بمتغيرات مباشرة داخل النمط:
enum Event {
Click { x: i32, y: i32 },
KeyPress(char),
Resize(u32, u32),
}
fn handle_event(event: Event) {
match event {
Event::Click { x, y } => {
println!("Clicked at ({}, {})", x, y);
}
Event::KeyPress(key) => {
println!("Key pressed: {}", key);
}
Event::Resize(width, height) => {
println!("Resized to {}x{}", width, height);
}
}
}
يستخرج كل ذراع البيانات بشكل مختلف بناءً على بنية المتغير. بالنسبة للمتغيرات الشبيهة بالهياكل (struct-like)، تستخدم { field_name } لاستخراج الحقول المسماة. أما بالنسبة لمتغيرات الصفوف (tuple)، فتستخدم متغيرات موضعية مثل (width, height).
تصبح أسماء المتغيرات التي تختارها متاحة داخل ذراع المطابقة (match arm) هذا. هذا أمر قوي لأنك تتحقق في الوقت نفسه من النوع (variant) الذي تملكه و تحصل على حق الوصول إلى البيانات المرتبطة به (payload).
يضمن المترجم أنك تتعامل مع كل حالة، وتكون القيم المستخرجة جاهزة للاستخدام فوراً - دون الحاجة لخطوات إضافية.
التحدي
سهللنقم ببناء معالج أوامر يتعامل مع أنواع مختلفة من أوامر المستخدم. يحمل كل أمر بيانات مختلفة، وسيقوم المعالج الخاص بك باستخدام مطابقة الأنماط (pattern matching) لاستخراج تلك البيانات وإنتاج الاستجابات المناسبة.
ستقوم بإنشاء ملفين لتنظيم الكود الخاص بك:
command.rs: قم بتعريف enum عام باسمCommandبثلاثة متغيرات (variants):Say— يحمل رسالةStringواحدة (صيغة tuple)Move— يحمل حقولاً مسماة:direction(String) وsteps(u32)Calculate— يحمل قيمتين من نوعi32تمثلان الأرقام المراد جمعها (صيغة tuple)
executeيستخدمmatchلتفكيك كل متغير وإرجاعStringيصف الإجراء المتخذ.main.rs: قم باستيراد وحدة الأوامر الخاصة بك، وأنشئ نسخة واحدة من كل متغير من متغيرات الأوامر باستخدام المدخلات المقدمة، واستدعِ تابعexecuteعلى كل منها لطباعة النتائج.
يجب أن يقوم تابع execute الخاص بك بتفكيك كل متغير للوصول إلى بياناته وإرجاع الرسائل بهذه التنسيقات الدقيقة:
- لـ
Say:Saying: {message} - لـ
Move:Moving {steps} steps {direction} - لـ
Calculate:{a} + {b} = {sum}
يجب أن يعرض مخرجك نتيجة كل أمر في سطر مستقل:
Saying: {message}
Moving {steps} steps {direction}
{a} + {b} = {sum}على سبيل المثال، مع رسالة say نصها "Hello world"، وتحرك بمقدار 5 خطوات "north"، وعملية حسابية لـ 10 و 25، سيكون المخرج:
Saying: Hello world
Moving 5 steps north
10 + 25 = 35ستتلقى خمسة مدخلات: رسالة say، والاتجاه، وعدد الخطوات، والرقمين المطلوب حسابهما.
ورقة مرجعية
تسمح لك مطابقة الأنماط باستخدام match بتفكيك متغيرات التعداد (enum variants) واستخراج بياناتها مباشرة في النمط.
بالنسبة لـ متغيرات الصف (tuple variants)، استخدم المتغيرات الموضعية:
enum Event {
KeyPress(char),
Resize(u32, u32),
}
match event {
Event::KeyPress(key) => {
println!("Key: {}", key);
}
Event::Resize(width, height) => {
println!("{}x{}", width, height);
}
}بالنسبة لـ المتغيرات الشبيهة بالهياكل (struct-like variants)، استخدم صيغة الحقول المسماة:
enum Event {
Click { x: i32, y: i32 },
}
match event {
Event::Click { x, y } => {
println!("Clicked at ({}, {})", x, y);
}
}تصبح المتغيرات المستخرجة متاحة داخل ذراع المطابقة (match arm) الخاص بها، مما يسمح لك بالتحقق من نوع المتغير والوصول إلى بياناته في آن واحد.
جرّب بنفسك
mod command;
use command::Command;
fn main() {
// قراءة المدخلات
let mut say_message = String::new();
std::io::stdin().read_line(&mut say_message).expect("Failed to read line");
let say_message = say_message.trim().to_string();
let mut direction = String::new();
std::io::stdin().read_line(&mut direction).expect("Failed to read line");
let direction = direction.trim().to_string();
let mut steps_input = String::new();
std::io::stdin().read_line(&mut steps_input).expect("Failed to read line");
let steps: u32 = steps_input.trim().parse().expect("Failed to parse steps");
let mut num1_input = String::new();
std::io::stdin().read_line(&mut num1_input).expect("Failed to read line");
let num1: i32 = num1_input.trim().parse().expect("Failed to parse num1");
let mut num2_input = String::new();
std::io::stdin().read_line(&mut num2_input).expect("Failed to read line");
let num2: i32 = num2_input.trim().parse().expect("Failed to parse num2");
// TODO: قم بإنشاء أمر Say باستخدام say_message وقم بتنفيذه
// TODO: قم بإنشاء أمر Move باستخدام direction و steps، وقم بتنفيذه
// TODO: قم بإنشاء أمر Calculate باستخدام num1 و num2، وقم بتنفيذه
// TODO: اطبع نتائج تنفيذ كل أمر
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس 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 مع المعاملاتملخص - مشغل الوسائط