إغلاق النوع العام في الصدأ


في هذه المقالة القصيرة ، سأتحدث عن نمط في Rust يسمح لك "بحفظ" لاستخدامه لاحقًا نوعًا مر عبر طريقة عامة. يوجد هذا النمط في مكتبات مصدر مكتبات Rust ، وأستخدمه أيضًا في بعض الأحيان في مشروعاتي. لم أتمكن من العثور على منشورات عنه في الشبكة ، لذا أعطيته اسمي: "إغلاق النوع المعمم" ، وفي هذه المقالة أريد أن أخبرك ما هو عليه ولماذا وكيف يمكن استخدامه.


المشكلة


في Rust ، يعد نظام الكتابة الثابتة وقدراته الساكنة كافياً ل 80٪ من الحالات على الأرجح. ولكن يحدث أن هناك حاجة إلى الكتابة الديناميكية عندما تريد تخزين كائنات من أنواع مختلفة في نفس المكان. تأتي كائنات أنواع الكائنات في عملية الإنقاذ هنا: فهي تمحو الأنواع الحقيقية للكائنات ، وتقللها إلى واجهة عامة معينة محددة بواسطة النوع ، وبعد ذلك يمكنك العمل على هذه الكائنات مثل كائنات أنواع الأنواع نفسها.


هذا يعمل بشكل جيد في نصف الحالات المتبقية. ولكن ماذا لو كنا ما زلنا بحاجة لاستعادة أنواع الكائنات المحوّة عند استخدامها؟ على سبيل المثال ، إذا كان سلوك كائناتنا يتحدد بنوع لا يمكن استخدامه ككائن كتابة . هذا موقف شائع للسمات ذات الأنواع المرتبطة. ماذا تفعل في هذه الحالة؟


قرار


بالنسبة لجميع الأنواع 'static (أي الأنواع التي لا تحتوي على ارتباطات غير ثابتة) ، يطبق Rust Any نوع ، والذي يسمح بتحويل dyn Any كائن كتابة إلى مرجع لنوع الكائن الأصلي:


 let value = "test".to_string(); let value_any = &value as &dyn Any; //       String.  //   -      . if let Some(as_string) = value_any.downcast_ref::<String>() { println!("String: {}", as_string); } else { println!("Unknown type"); } 

بداية


Box لديه أيضا طريقة downcast لهذا الغرض.


هذا الحل مناسب لتلك الحالات عندما يكون نوع المصدر معروفًا في مكان العمل به. ولكن ماذا لو لم يكن الأمر كذلك؟ ماذا لو كان رمز الاتصال ببساطة لا يعرف عن نوع مصدر الكائن في مكان استخدامه؟ ثم نحتاج أن نتذكر بطريقة أو بأخرى النوع الأصلي ، وأن نأخذه حيثما تم تعريفه ، وأن dyn Any مع كائن dyn Any type ، بحيث يتم تحويل الأخير إلى النوع الأصلي في المكان المناسب.


يمكن التعامل مع الأنواع المعممة في Rust كمتغيرات كتابة يمكن من خلالها تمرير قيمة نوع أو آخر عند استدعائها. لكن في Rust لا توجد وسيلة لتذكر هذا النوع للاستخدام مرة أخرى في أي مكان آخر. ومع ذلك ، هناك طريقة لتذكر كل الوظائف باستخدام هذا النوع ، إلى جانب هذا النوع. هذه هي فكرة نمط "إغلاق نوع معمم": يتم تنفيذ التعليمات البرمجية باستخدام نوع في شكل إغلاق ، يتم حفظه كدالة عادية ، لأنه لا يستخدم أي كائنات في البيئة باستثناء الأنواع المعممة.


تطبيق


لنلقِ نظرة على مثال للتنفيذ. لنفترض أننا نريد إنشاء شجرة عودية تمثل تسلسل هرمي للكائنات الرسومية ، حيث يمكن أن تكون كل عقدة إما بدائية بيانية ذات عقد فرعية ، أو مكونًا - شجرة منفصلة من الكائنات الرسومية:


 enum Node { Prim(Primitive), Comp(Component), } struct Primitive { shape: Shape, children: Vec<Node>, } struct Component { node: Box<Node>, } enum Shape { Rectangle, Circle, } 

Node تغليف Node في بنية Component ضروريًا لأن بنية Component نفسه يُستخدم في Node .


لنفترض الآن أن شجرتنا هي مجرد تمثيل لبعض النماذج التي يجب أن ترتبط بها. علاوة على ذلك ، سيكون لكل مكون نموذج خاص به:


 struct Primitive<Model> { shape: Shape, children: Vec<Node<Model>>, } struct Component<Model> { node: Box<Node<Model>>, model: Model, //   Model } 

يمكن أن نكتب:


 enum Node<Model> { Prim(Primitive<Model>), Comp(Component<Model>), } 

لكن هذا الرمز لن يعمل كما نحتاج. لأنه يجب أن يكون للمكون نموذج خاص به ، وليس طراز العنصر الأصل ، الذي يحتوي على المكون. هذا هو ، نحن بحاجة إلى:


 enum Node<Model> { Prim(Primitive<Model>), Comp(Component), } struct Primitive<Model> { shape: Shape, children: Vec<Node<Model>>, _model: PhantomData<Model>, //   Model } struct Component { node: Box<dyn Any>, model: Box<dyn Any>, } impl Component { fn new<Model: 'static>(node: Node<Model>, model: Model) -> Self { Self { node: Box::new(node), model: Box::new(model), } } } 

بداية


لقد نقلنا الإشارة إلى نوع معين من النموذج إلى الطريقة new ، وفي المكون نفسه نقوم بتخزين النموذج والشجرة الفرعية بالفعل مع أنواع تمحى.


أضف الآن use_model method ، التي ستستخدم النموذج ، ولكن لن يتم تحديدها حسب نوعه:


 struct Component { node: Box<dyn Any>, model: Box<dyn Any>, use_model_closure: fn(&Component), } impl Component { fn new<Model: 'static>(node: Node<Model>, model: Model) -> Self { let use_model_closure = |comp: &Component| { comp.model.downcast_ref::<Model>().unwrap(); }; Self { node: Box::new(node), model: Box::new(model), use_model_closure, } } fn use_model(&self) { (self.use_model_closure)(self); } } 

بداية


لاحظ أننا في المكون نقوم بتخزين مؤشر إلى دالة يتم إنشاؤها في الطريقة new باستخدام بناء الجملة لتحديد الإغلاق. لكن كل ما يجب التقاطه من الخارج هو نوع Model ، لذلك نحن مضطرون لتمرير رابط للمكون نفسه في هذه الوظيفة من خلال وسيطة.


يبدو أنه بدلاً من الإغلاق ، يمكننا استخدام وظيفة داخلية ، لكن هذا الرمز لا يتم تجميعه. لأن الوظيفة الداخلية في Rust لا يمكنها التقاط الأنواع المعممة من الخارجية بسبب حقيقة أنها تختلف عن وظيفة المستوى الأعلى المعتادة في الرؤية فقط.

يمكن use_model استخدام طريقة use_model في سياق Model يعرف فيه النوع الفعلي use_model . على سبيل المثال ، في اجتياز شجرة عودية يتكون من العديد من المكونات المختلفة مع نماذج مختلفة.


البديل


إذا كان من الممكن نقل واجهة المكون إلى نوع يسمح بإنشاء كائن كتابة ، فمن الأفضل القيام بذلك ، وبدلاً من ذلك استخدم المكون نفسه للعمل على كائن الكتابة الخاص به:


 enum Node<Model> { Prim(Primitive<Model>), Comp(Box<dyn ComponentApi>), } struct Component<Model> { node: Node<Model>, model: Model, } impl<Model> Component<Model> { fn new(node: Node<Model>, model: Model) -> Self { Self { node, model, } } } trait ComponentApi { fn use_model(&self); } impl<Model> ComponentApi for Component<Model> { fn use_model(&self) { &self.model; } } 

بداية


استنتاج


اتضح أن عمليات الإغلاق في Rust لا يمكنها التقاط كائنات البيئة فقط ، ولكن أيضًا الأنواع. ومع ذلك ، يمكن تفسيرها على أنها وظائف عادية. تصبح هذه الخاصية مفيدة عندما تحتاج إلى العمل بشكل موحد مع أنواع مختلفة دون فقد المعلومات عنها ، إذا كانت أنواع الأحرف غير قابلة للتطبيق.


آمل أن يكون هذا المقال يساعدك على استخدام الصدأ. شارك أفكارك في التعليقات.

Source: https://habr.com/ru/post/ar454310/


All Articles