Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

نمط Newtype

جزء من قسم Object Oriented Programming في رحلة Rust على Coddy — الدرس 54 من 61.

أحيانًا يكون لديك قيمتان من نفس النوع تمثلان أشياء مختلفة تمامًا. قد يكون كل من البريد الإلكتروني للمستخدم وكلمة مروره قيمًا من النوع String، لكن تبديلهما عن طريق الخطأ في استدعاء دالة سيكون خطأً برمجياً خطيراً. يعالج نمط Newtype هذا الأمر من خلال إنشاء أنواع متميزة تغلف الأنواع الأولية (primitives).

النوع الجديد (newtype) هو ببساطة هيكل صف (tuple struct) يحتوي على حقل واحد فقط:

struct Password(String);
struct Email(String);

على الرغم من أن كليهما يغلفان String، إلا أن المترجم يعاملهما كأنواع مختلفة تماماً. لا يمكنك تمرير Password عن طريق الخطأ حيث يُتوقع Email—سيكتشف المترجم الخطأ.

fn send_reset_email(email: Email, password: Password) {
    // ...
}

let email = Email(String::from("user@example.com"));
let pass = Password(String::from("secret123"));

send_reset_email(email, pass);  // الترتيب الصحيح مفروض بواسطة الأنواع
// send_reset_email(pass, email);  // لن يتم التجميع!

للوصول إلى القيمة الداخلية، استخدم .0 بما أنه tuple struct:

let pass = Password(String::from("secret123"));
println!("Length: {}", pass.0.len());

يمكنك أيضًا إضافة دوال (methods) إلى الـ newtype الخاص بك، مما يمنحه سلوكًا خاصًا بما يمثله — مثل التحقق من الصحة أو التنسيق — وهو ما لن يكون منطقيًا في String عادي.

challenge icon

التحدي

سهل

لنقم ببناء نظام تسجيل مستخدمين آمن من حيث الأنواع (type-safe) باستخدام نمط Newtype! ستقوم بإنشاء أنواع متميزة لـ Username و UserId لمنع الخلط بينهما عن طريق الخطأ - على الرغم من أن كليهما يغلفان قيمًا بسيطة.

ستقوم بتنظيم الكود الخاص بك عبر ملفين:

  • user_types.rs: قم بتعريف هيكلين (structs) عامين من نوع newtype - Username يغلف String و UserId يغلف u32. أضف دالة (method) تسمى value لكل منهما تعيد مرجعًا (reference) للبيانات الداخلية (بالنسبة لـ Username، أعد &String؛ وبالنسبة لـ UserId، أعد &u32). يوفر هذا وصولاً محكومًا للقيم المغلفة.
  • main.rs: قم باستيراد وحدة user_types الخاصة بك وأنشئ دالة تسمى display_user تأخذ UserId كمعامل أول و Username كمعامل ثانٍ. يجب أن تقوم الدالة بطباعة معلومات المستخدم. ثم قم بإنشاء مثيلات (instances) من كلا النوعين باستخدام المدخلات المقدمة واستدعِ الدالة الخاصة بك.

الفكرة الأساسية هنا هي أنه على الرغم من أن UserId و Username هما مجرد مغلفات بسيطة، إلا أن المترجم (compiler) يعاملهما كأنواع مختلفة تمامًا. لا يمكنك تمرير اسم مستخدم عن طريق الخطأ حيث يُتوقع معرف (ID)!

يجب أن تقوم دالة display_user بالطباعة بهذا التنسيق:

User #{id}: {username}

على سبيل المثال، مع المدخلات 42 و alice_dev:

User #42: alice_dev

ومع المدخلات 1001 و bob_smith:

User #1001: bob_smith

ستتلقى مدخلين: معرف المستخدم (قم بتحليله كـ u32) وسلسلة اسم المستخدم.

جرّب بنفسك

mod user_types;

use user_types::{Username, UserId};

// مهمة: قم بإنشاء دالة تسمى `display_user` تأخذ:
// - المعامل الأول: UserId
// - المعامل الثاني: Username
// يجب أن تطبع الدالة: "User #{id}: {username}"
// استخدم طريقة .value() للوصول إلى القيم الداخلية


fn main() {
    // قراءة المدخلات
    let mut id_input = String::new();
    std::io::stdin().read_line(&mut id_input).expect("Failed to read line");
    let id: u32 = id_input.trim().parse().expect("Invalid number");
    
    let mut username_input = String::new();
    std::io::stdin().read_line(&mut username_input).expect("Failed to read line");
    let username = username_input.trim().to_string();
    
    // مهمة: قم بإنشاء مثيلات من UserId و Username باستخدام المدخلات
    
    // مهمة: قم باستدعاء display_user مع الوسائط الصحيحة
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming