حاويات حقن التبعية (Dependency Injection) لـ Rust: نظرة عامة على الأدوات والممارسات

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

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

ابدأ الدورة

حاويات حقن التبعية لـ Rust: إدارة التبعيات في البرمجة النظامية

\n\n

في عالم البرمجة النظامية، تشتهر Rust بأدائها العالي وأمان الذاكرة. ومع ذلك، مع نمو المشروع، تظهر مهمة إدارة الرسوم البيانية المعقدة للتبعيات. على عكس اللغات ذات إدارة الذاكرة التلقائية (Java, C#, Python)، تتطلب Rust نهجًا خاصًا لحقن التبعيات (Dependency Injection, DI). في هذه المقالة، سنستعرض الأدوات والمكتبات الموجودة لتنفيذ حاويات DI في Rust، وكيفية استخدامها بشكل صحيح.

\n\n

لماذا نحتاج إلى حاوية DI في Rust؟

\n\n

يسمح نمط حقن التبعية بفصل إنشاء الكائنات عن استخدامها. في Rust، هذا مهم بشكل خاص بسبب نظام الملكية والاقتراض الصارم. تحل حاوية DI عدة مهام:

\n\n
    \n
  • تبسيط الاختبار — إمكانية استبدال التطبيقات الحقيقية بـ mocks
  • \n
  • تقليل الترابط في الكود — تعتمد المكونات على التجريدات وليس على التطبيقات الملموسة
  • \n
  • إدارة مركزية لدورة الحياة — الكائنات المفردة (singletons)، الكائنات المؤقتة، التجمعات
  • \n
  • الحل التلقائي للتبعيات — تقوم الحاوية ببناء الرسم البياني للكائنات بنفسها
  • \n
\n\n

المكتبات الرئيسية لـ DI في Rust

\n\n

1. shaku — حاوية DI بسيطة وفعالة

\n\n

واحدة من أشهر المكتبات. تعتمد على traits والماكرو. تسمح بتسجيل المكونات بأعمار مختلفة.

\n\n
// Cargo.toml\n// [dependencies]\n// shaku = "0.6"
\n\n
use shaku::{module, HasComponent, Interface};\nuse std::sync::Arc;\n\n// تعريف الواجهة\ntrait Logger: Interface {\n    fn log(&self, msg: &str);\n}\n\n// التطبيق\n#[derive(Clone)]\nstruct ConsoleLogger;\nimpl Logger for ConsoleLogger {\n    fn log(&self, msg: &str) {\n        println!("LOG: {}", msg);\n    }\n}\n\n// وحدة التسجيل\nmodule! {\n    MyModule {\n        components = [ConsoleLogger],\n        providers = []\n    }\n}\n\n// الاستخدام\nfn main() {\n    let module = MyModule::builder().build();\n    let logger: Arc<dyn Logger> = module.resolve().unwrap();\n    logger.log("Hello from DI!");\n}
\n\n

2. dyn-clone + حاوية يدوية

\n\n

إذا كنت تفضل البساطة، يمكنك إنشاء حاوية بسيطة يدويًا باستخدام Any و dyn-clone. هذا يمنحك تحكمًا كاملاً في العملية.

\n\n
use std::any::Any;\nuse std::collections::HashMap;\n\nstruct SimpleContainer {\n    services: HashMap<String, Box<dyn Any>>,\n}\n\nimpl SimpleContainer {\n    fn new() -> Self {\n        SimpleContainer { services: HashMap::new() }\n    }\n\n    fn register<T: 'static>(&mut self, service: T) {\n        let type_name = std::any::type_name::<T>().to_string();\n        self.services.insert(type_name, Box::new(service));\n    }\n\n    fn resolve<T: 'static>(&self) -> Option<&T> {\n        let type_name = std::any::type_name::<T>().to_string();\n        self.services.get(&type_name)\n            .and_then(|s| s.downcast_ref::<T>())\n    }\n}
\n\n

3. once_cell و lazy_static — للكائنات المفردة (singletons)

\n\n

غالبًا ما يقتصر DI على إدارة الحالات العامة. تساعد هذه المكتبات في إنشاء كائنات مفردة يتم تهيئتها بشكل كسول (lazy).

\n\n
use once_cell::sync::Lazy;\nuse std::sync::Mutex;\n\nstatic CONFIG: Lazy<Mutex<Config>> = Lazy::new(|| {\n    Mutex::new(Config::load())\n});\n\nstruct Config {\n    db_url: String,\n}\n\nimpl Config {\n    fn load() -> Self {\n        Config { db_url: "postgres://localhost".into() }\n    }\n}
\n\n

الأنماط المعمارية لـ DI في Rust

\n\n

في Rust، غالبًا ما يتم استخدام Constructor Injection — حيث يتم تمرير التبعيات عبر مُنشئ الهيكل. هذا هو الأسلوب الأكثر توافقًا مع

توصيات