سينجلتون عالمي في Rust: تحطيم الصور النمطية مع الكود

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

مقدمة: لماذا يُعتبر السينجلتون العالمي في Rust تحديًا؟

\n\n

إذا كنت قادمًا من عالم Python أو JavaScript أو C++، فأنت معتاد على كائن عالمي بسيط. تعلن عن متغير على مستوى الوحدة النمطية - ويكون ملكك. في Rust، هذه الخدعة لن تنجح. يراقب المترجم بدقة عمر البيانات والوصول إليها، خاصة إذا كانت قابلة للتغيير من خيوط مختلفة. لهذا السبب بالتحديد، جمع سؤال Stack Overflow «How do I create a global, mutable singleton?» آلاف المشاهدات وعشرات الإجابات المعقدة.

\n\n

لكن لا تتعجل في الخوف. السينجلتون في Rust ليس ألمًا، بل انضباط. اليوم سنشرح كيفية إنشاء سينجلتون عالمي قابل للتغيير بأمان وكفاءة مع فهم ما يحدث. ستتعرف على لماذا static mut هو شر، وما هي الأدوات (lazy_static، OnceCell، Mutex) التي تمنحنا السيطرة.

\n\n

المقالة مكتوبة للموقع التعليمي pythonlib.ru وتكييف سؤال حقيقي من SO إلى درس عملي. هيا بنا!

\n\n

المشكلة: ما هو السينجلتون العالمي القابل للتغيير؟

\n\n

السينجلتون هو نمط يكون فيه للفئة أو الهيكل نسخة واحدة بالضبط في جميع أنحاء البرنامج. في سياق Rust، نحن نتحدث عن متغير عالمي يمكن الوصول إليه من أي مكان في الكود، ويمكن تغييره (mut).

\n\n

أمثلة على الاستخدام:

\n
    \n
  • تكوين التطبيق، الذي يتم تحميله مرة واحدة عند بدء التشغيل.
  • \n
  • مسجل عالمي أو عداد أحداث.
  • \n
  • مجموعة اتصالات بقاعدة البيانات.
  • \n
  • ذاكرة تخزين مؤقت في الذاكرة (in-memory cache).
  • \n
\n\n

المشكلة هي أن Rust يمنع المتغيرات العالمية القابلة للتغيير بدون غلاف خاص. لماذا؟ لأنها تؤدي إلى سباقات البيانات (data races) وسلوك غير محدد. لا يستطيع المترجم إثبات أن خيطين لن يكتبا في نفس خلية الذاكرة في نفس الوقت.

\n\n

الحل الساذج يبدو هكذا:

\n
// كود سَيء - لا تفعل هذا!\nstatic mut COUNTER: u32 = 0;\n\nfn increment() {\n    unsafe {\n        COUNTER += 1;\n    }\n}\n
\n

هذا يعمل، لكنه يتطلب unsafe ويتركك وحدك مع سباقات البيانات. في بيئة متعددة الخيوط - هذه قنبلة موقوتة. نحتاج إلى نهج آمن ومنسجم مع المصطلحات.

\n\n

الأداة 1: lazy_static + Mutex - كلاسيكيات هذا النوع

\n\n

الطريقة الأكثر شيوعًا لإنشاء سينجلتون عالمي قابل للتغيير هي الجمع بين lazy_static (التهيئة البطيئة) و Mutex (الاستبعاد المتبادل).

\n\n

أضف التبعيات في Cargo.toml:

\n
[dependencies]\nlazy_static = "1.4"\n
\n\n

الآن الكود:

\n
use lazy_static::lazy_static;\nuse std::sync::Mutex;\n\nlazy_static! {\n    static ref GLOBAL_CONFIG: Mutex<Config> = Mutex::new(Config::default());\n}\n\n#[derive(Debug)]\nstruct Config {\n    db_url: String,\n    max_connections: u32,\n}\n\nimpl Default for Config {\n    fn default() -> Self {\n        Config {\n            db_url: "localhost:5432".to_string(),\n            max_connections: 10,\n        }\n    }\n}\n\nfn main() {\n    // قراءة\n    let config = GLOBAL_CONFIG.lock().unwrap();\n    println!("الرابط الحالي: {}", config.db_url);\n\n    // كتابة\n    // drop(config); // تحرير القفل صراحةً\n    let mut config = GLOBAL_CONFIG.lock().unwrap();\n    config.db_url = "new-host:8888".to_string();\n    println!("الرابط الجديد: {}", config.db_url);\n}\n
\n\n

كيف يعمل هذا؟

\n
    \n
  • lazy_static! يضمن أن التهيئة ستحدث مرة واحدة فقط عند أول وصول.
  • \n
  • Mutex<T> يوفر الاستبعاد المتبادل: يمكن لخيط واحد فقط الوصول إلى البيانات في أي لحظة.
  • \n
  • الطريقة .lock() ترجع MutexGuard، الذي يحرر القفل تلقائيًا عند الخروج من النطاق.
  • \n
\n\n

الإيجابيات: البساطة، مجربة عبر الزمن، آمنة لـ

المدونات

توصيات الكتب