غطينا مفهوم السمات سابقًا في فصل السمات Traits في لغة رست Rust، إلا أننا لم نناقش التفاصيل الأكثر تقدمًا. سنخوض الآن بالتفاصيل الجوهرية بعد أن تعملت المزيد عن لغة رست حتى الآن.
تحديد أنواع الموضع المؤقت في تعريفات السمات مع الأنواع المرتبطة
تصل الأنواع المرتبطة associated types نوع موضع مؤقت placeholder بسمة بحيث يمكن لتعريفات تابع السمة استخدام أنواع المواضع المؤقتة هذه في بصماتها signature، وسيحدد منفّذ السمة النوع الحقيقي الذي سيُستخدم بدلًا من نوع الموضع المؤقت للتنفيذ المعين. يمكننا بهذه الطريقة تحديد سمة تستخدم بعض الأنواع دون الحاجة إلى معرفة ماهية هذه الأنواع تحديدًا حتى تُطبَّق السمة.
وصفنا معظم الميزات المتقدمة في هذا الفصل على أنها نادرًا ما تكون مطلوبة. توجد الأنواع المرتبطة في مكان ما في الوسط، إذ تُستخدَم نادرًا أكثر من الميزات الموضحة سابقًا في بقية الكتاب ولكنها أكثر شيوعًا من العديد من الميزات المتقدمة الأخرى التي نوقشت في هذا الفصل.
أحد الأمثلة على سمة ذات نوع مرتبط هي سمة Iterator
التي توفرها المكتبة القياسية. يُطلق على النوع المرتبط اسم Item
ويرمز إلى نوع القيم التي يمرّ عليها النوع الذي ينفّذ سمة Iterator
. تُعرّف سمة Iterator
كما هو موضح في الشيفرة 12.
pub trait Iterator { type Item; fn next(&mut self) -> Option<Self::Item>; }
[الشيفرة 12: تعريف سمة Iterator
التي لديها نوع مرتبط Item
]
النوع Item
هو موضع مؤقت، ويوضح تعريف تابع next
أنه سيعيد قيمًا من النوع <Option<Self::Item
. سيحدد منفّذ سمة Iterator
النوع الحقيقي للنوع Item
وسيُعيد التابع next
النوع Option
الذي يحتوي على قيمة من هذا النوع الحقيقي.
قد تبدو الأنواع المرتبطة مثل مفهوم مشابه للأنواع المعممة من حيث أن الأخير يسمح لنا بتعريف دالة دون تحديد الأنواع التي يمكنها التعامل معها، ولفحص الاختلاف بين المفهومين، سنلقي نظرةً على تنفيذ سمة Iterator
على نوع يسمى Counter
الذي يحدد نوع Item
هو u32
:
اسم الملف: src/lib.rs
impl Iterator for Counter { type Item = u32; fn next(&mut self) -> Option<Self::Item> { // --snip--
تبدو هذه الصيغة مشابهة لتلك الموجودة في الأنواع المعمّمة generic، فلماذا لا نكتفي بتعريف سمة Iterator
باستخدام الأنواع المعممة كما هو موضح في الشيفرة 13؟
pub trait Iterator<T> { fn next(&mut self) -> Option<T>; }
[الشيفرة 13: تعريف افتراضي لسمة Iterator
باستخدام الأنواع المعممة]
الفرق هو أنه علينا أن نوضح الأنواع في كل تنفيذ عند استخدام الأنواع المعمّمة كما في الشيفرة 13، لأنه يمكننا أيضًا تنفيذ Iterator<String> for Counter
أو أي نوع آخر، فقد يكون لدينا تنفيذات متعددة للسمة Iterator
من أجل Counter
. بعبارة أخرى: عندما تحتوي السمة على معامل معمّم، يمكن تنفيذه لنوع ما عدة مرات مع تغيير الأنواع الحقيقية لمعاملات النوع المعمم في كل مرة، وعندما نستخدم التابع next
على Counter
سيتوجب علينا توفير التعليقات التوضيحية من النوع للإشارة إلى تنفيذ Iterator
الذي نريد استخدامه.
لا نحتاج مع الأنواع المرتبطة إلى إضافة تعليقات توضيحية للأنواع، لأننا لا نستطيع تنفيذ سمة على نوع عدة مرات. يمكننا فقط اختيار نوع Item
مرةً واحدةً في الشيفرة 12 مع التعريف الذي يستخدم الأنواع المرتبطة لأنه لا يمكن أن يكون هناك سوى impl Iterator for Counter
واحد. لا يتعين علينا تحديد أننا نريد مكررًا لقيم u32
في كل مكان نستدعي next
على Counter
.
تصبح الأنواع المرتبطة أيضًا جزءًا من عقد contract السمة، إذ يجب أن يوفر منفّذو السمة نوعًا لملء الموضع المؤقت للنوع المرتبط. تمتلك الأنواع المرتبطة غالبًا اسمًا يصف كيفية استخدام النوع، كما يُعد توثيق النوع المرتبط في توثيق الواجهة البرمجية ممارسةً جيدة.
معاملات النوع المعمم الافتراضي وزيادة تحميل العامل
عندما نستخدم معاملات النوع المعمم يمكننا تحديد نوع حقيقي افتراضي للنوع المعمم، وهذا يلغي الحاجة إلى منفّذي السمة لتحديد نوع حقيقي إذا كان النوع الافتراضي يعمل. يمكنك تحديد نوع افتراضي عند التصريح عن نوع عام باستخدام الصيغة <PlaceholderType=ConcreteType>
.
من الأمثلة الرائعة على الموقف الذي تكون فيه هذه التقنية مفيدة هو زيادة تحميل العامل operator overloading، إذ يمكنك تخصيص سلوك عامل (مثل +
) في مواقف معينة.
لا تسمح لك رست بإنشاء عواملك الخاصة أو زيادة تحميل العوامل العشوائية، ولكن يمكنك زيادة تحميل العمليات والسمات المقابلة المدرجة في std::ops
من خلال تنفيذ السمات المرتبطة بالعامل. على سبيل المثال في الشيفرة 14 زدنا تحميل العامل +
لإضافة نسختين Point
معًا عن طريق تنفيذ سمة Add
على بنية Point
.
اسم الملف: src/main.rs
use std::ops::Add; #[derive(Debug, Copy, Clone, PartialEq)] struct Point { x: i32, y: i32, } impl Add for Point { type Output = Point; fn add(self, other: Point) -> Point { Point { x: self.x + other.x, y: self.y + other.y, } } } fn main() { assert_eq!( Point { x: 1, y: 0 } + Point { x: 2, y: 3 }, Point { x: 3, y: 3 } ); }
[الشيفرة 14: تنفيذ سمة Add
لزيادة تحميل العامل +
لنسخ Point
]
يضيف التابع add
قيم x
لنسختَي Point
وقيم y
لنسختَي Point
لإنشاء Point
جديدة. للسمة Add
نوع مرتبط يسمى Output
الذي يحدد النوع الذي يُعاد من التابع add
.
النوع المعمم الافتراضي في هذه الشيفرة موجودٌ ضمن سمة Add
. إليك تعريفه:
trait Add<Rhs=Self> { type Output; fn add(self, rhs: Rhs) -> Self::Output; }
يجب أن تبدو هذه الشيفرة مألوفة عمومًا: سمة ذات تابع واحد ونوع مرتبط بها. الجزء الجديد هو Rhs=Self
، إذ يُطلق على الصيغة هذه معاملات النوع الافتراضية default type parameters. يعرّف معامل النوع المعمم Rhs
(اختصار للجانب الأيمن right hand size) نوع المعامل rhs
في تابع add
. إذا لم نحدد نوعًا حقيقيًا للقيمة Rhs
عند تنفيذ سمة Add
، سيُعيّن نوع Rhs
افتراضيًا إلى Self
الذي سيكون النوع الذي ننفّذ السمة Add
عليه.
عندما نفّذنا Add
على Point
استخدمنا الإعداد الافتراضي للنوع Rhs
لأننا أردنا إضافة نسختين Point
. لنلقي نظرةً على مثال لتنفيذ سمة Add
، إذ نريد تخصيص نوع Rhs
بدلًا من الاستخدام الافتراضي.
لدينا الهيكلان Millimeters
و Meters
اللذان يحملان قيمًا في وحدات مختلفة، يُعرف هذا الغلاف الرقيق لنوع موجود في هيكل آخر باسم نمط النوع الجديد newtype pattern الذي نصفه بمزيد من التفصيل لاحقًا في قسم "استخدام نمط النوع الجديد لتنفيذ السمات الخارجية على الأنواع الخارجية". نريد أن نضيف القيم بالمليمترات إلى القيم بالأمتار وأن نجعل تنفيذ Add
يجري التحويل صحيحًا. يمكننا تنفيذ Add
للنوع Millimeters
مع Meters
مثل Rhs
كما هو موضح في الشيفرة 15.
اسم الملف: src/lib.rs
use std::ops::Add; struct Millimeters(u32); struct Meters(u32); impl Add<Meters> for Millimeters { type Output = Millimeters; fn add(self, other: Meters) -> Millimeters { Millimeters(self.0 + (other.0 * 1000)) } }
[الشيفرة 15: تنفيذ سمة Add
على Millimeters
لإضافة Millimeters
للنوع Meters
]
لإضافة Millimeters
و Meters
نحدد <impl Add<Meters
لتعيين قيمة محدد نوع Rhs
بدلًا من استخدام الافتراضي Self
.
ستستخدم معاملات النوع الافتراضية بطريقتين رئيسيتين:
- لتوسيع نوع دون تعطيل الشيفرة الموجودة.
- للسماح بالتخصيص في حالات معينة لن يحتاجها معظم المستخدمين.
تعد سمة المكتبة القياسية Add
مثالًا على الغرض الثاني: ستضيف عادةً نوعين متشابهين، بينما توفّر خاصية Add
القدرة على التخصيص بعد ذلك. يعني استخدام معامل النوع الافتراضي في تعريف سمة Add
أنك لست مضطرًا لتحديد المعامل الإضافي في معظم الأوقات. بعبارة أخرى ليست هناك حاجة إلى القليل من التنفيذ المعياري، وهذا ما يسهل استخدام السمة.
صيغة مؤهلة كليا للتوضيح باستدعاء التوابع التي تحمل الاسم ذاته
لا شيء في رست يمنع سمةً ما من أن يكون لها تابع يحمل اسم تابع السمة الأخرى ذاتها، كما لا تمنعك رست من تنفيذ كلتا السمتين على نوع واحد، ومن الممكن أيضًا تنفيذ تابع مباشرةً على النوع الذي يحمل اسم التوابع نفسه من السمات.
عند استدعاء التوابع التي تحمل الاسم نفسه، ستحتاج إلى إخبار رست بالتابع الذي تريد استخدامه. ألقِ نظرةً على الشيفرة 16، إذ عرّفنا سمتين Pilot
و Wizard
ولكل منهما تابع يسمى fly
، ثم نفّذنا كلتا السمتين على نوع Human
لديه فعلًا تابع يسمى fly
منفّذ عليه، وكل تابع fly
يفعل شيئًا مختلفًا.
اسم الملف: src/main.rs
trait Pilot { fn fly(&self); } trait Wizard { fn fly(&self); } struct Human; impl Pilot for Human { fn fly(&self) { println!("This is your captain speaking."); } } impl Wizard for Human { fn fly(&self) { println!("Up!"); } } impl Human { fn fly(&self) { println!("*waving arms furiously*"); } }
[الشيفرة 16: تعريف سمتين بحيث تملكان تابع fly
وتنفيذهما على النوع Human
وتنفيذ تابع fly
على Human
مباشرةً]
عندما نستدعي fly
على نسخة Human
يتخلف المصرف عن استدعاء التابع الذي يُنفّذ مباشرةً على النوع كما هو موضح في الشيفرة 17.
اسم الملف: src/main.rs
fn main() { let person = Human; person.fly(); }
[الشيفرة 17: استدعاء fly
على نسخة Human
]
سيؤدي تنفيذ هذه الشيفرة إلى طباعة *waving arms furiously*
مما يدل على أن رست استدعت تابع fly
المنفّذ على Human
مباشرةً.
نحتاج إلى استخدام صيغة أكثر وضوحًا عند استدعاء توابع fly
من سمة Pilot
أو Wizard
لتحديد تابع fly
الذي نعنيه، وتوضّح الشيفرة 18 هذه الصيغة.
اسم الملف: src/main.rs
fn main() { let person = Human; Pilot::fly(&person); Wizard::fly(&person); person.fly(); }
[الشيفرة 18: تحديد تابع السمة fly
التي نريد استدعاءه]
يوضح تحديد اسم السمة قبل اسم التابع لرست أي تنفيذ للتابع fly
نريد استدعاءه. يمكننا أيضًا كتابة Human::fly(&person)
وهو ما يعادل person.fly()
الذي استخدمناه في الشيفرة 18، ولكن هذا أطول قليلًا للكتابة إذا لم نكن بحاجة إلى توضيح.
يؤدي تنفيذ هذه الشيفرة إلى طباعة التالي:
$ cargo run Compiling traits-example v0.1.0 (file:///projects/traits-example) Finished dev [unoptimized + debuginfo] target(s) in 0.46s Running `target/debug/traits-example` This is your captain speaking. Up! *waving arms furiously*
إذا كان لدينا نوعان ينفذ كلاهما سمةً واحدةً، يمكن أن تكتشف رست أي تنفيذ للسمة يجب استخدامه بناءً على نوع self
نظرًا لأن تابع fly
يأخذ معامل self
، ومع ذلك فإن الدوال functions المرتبطة التي ليست بتوابع لا تحتوي على معامل self
. عندما تكون هناك أنواع أو سمات متعددة تحدد دوالًا غير تابعية non-method بنفس اسم الدالة، لا تعرف رست دائمًا النوع الذي تقصده ما لم تستخدم صيغة مؤهلة كليًا fully qualified syntax. على سبيل المثال في الشيفرة 19 أنشأنا سمة لمأوى للحيوانات يسمّي جميع الجراء puppies الصغار Spot. ننشئ سمة Animal
بدالة غير تابعية مرتبطة baby_name
، تُنفّذ Animal
لهيكل Dog
الذي نوفّر عليه أيضًا دالةً غير تابعية مرتبطة baby_name
مباشرةً.
اسم الملف: src/main.rs
trait Animal { fn baby_name() -> String; } struct Dog; impl Dog { fn baby_name() -> String { String::from("Spot") } } impl Animal for Dog { fn baby_name() -> String { String::from("puppy") } } fn main() { println!("A baby dog is called a {}", Dog::baby_name()); }
[الشيفرة 19: سمة لها دالة مرتبطة ونوع له دالة مرتبطة بالاسم ذاته الذي ينفّذ السمة أيضًا]
ننفّذ الشيفرة الخاص بتسمية كل الجراء في الدالة المرتبطة baby_name
والمُعرّفة في Dog
. ينفّذ النوع Dog
أيضًا سمة Animal
التي تصف الخصائص التي تمتلكها جميع الحيوانات. يُطلق على أطفال الكلاب اسم الجراء ويُعبّر عن ذلك في تنفيذ سمة Animal
على Dog
في الدالة المرتبطة baby_name
بالسمة Animal
نستدعي في الدالة main
الدالة Dog::baby_name
التي تستدعي الدالة المرتبطة المعرفة في Dog
مباشرةً. تطبع هذه الشيفرة ما يلي:
$ cargo run Compiling traits-example v0.1.0 (file:///projects/traits-example) Finished dev [unoptimized + debuginfo] target(s) in 0.54s Running `target/debug/traits-example` A baby dog is called a Spot
لم نكن نتوقع هذه النتيجة، إذ نريد استدعاء دالة baby_name
التي تعد جزءًا من سمة Animal
التي نفّذناها على Dog
حتى تطبع الشيفرة A baby dog is called a puppy
. لا تساعد تقنية تحديد اسم السمة التي استخدمناها في الشيفرة 18 هنا، وإذا غيرنا main
للشيفرة لما هو موجود في الشيفرة 20، سنحصل على خطأ عند التصريف.
اسم الملف: src/main.rs
fn main() { println!("A baby dog is called a {}", Animal::baby_name()); }
[الشيفرة 20: محاولة استدعاء الدالة baby_name
من السمة Animal
دون معرفة رست بأي تنفيذ ينبغي استخدامه]
لا تستطيع رست معرفة أي تنفيذ نريده للقيمة Animal::baby_name
لأن Animal::baby_name
لا تحتوي على معامل self
ولأنه يمكن أن تكون هناك أنواع أخرى تنفّذ سمة Animal
. سنحصل على خطأ المصرف هذا:
$ cargo run Compiling traits-example v0.1.0 (file:///projects/traits-example) error[E0790]: cannot call associated function on trait without specifying the corresponding `impl` type --> src/main.rs:20:43 | 2 | fn baby_name() -> String; | ------------------------- `Animal::baby_name` defined here ... 20 | println!("A baby dog is called a {}", Animal::baby_name()); | ^^^^^^^^^^^^^^^^^ cannot call associated function of trait | help: use the fully-qualified path to the only available implementation | 20 | println!("A baby dog is called a {}", <Dog as Animal>::baby_name()); | +++++++ + For more information about this error, try `rustc --explain E0790`. error: could not compile `traits-example` due to previous error
لإزالة الغموض وإخبار رست أننا نريد استخدام تنفيذ Animal
للقيمة Dog
بدلًا من استخدام Animal
لبعض الأنواع الأخرى، نحتاج إلى استخدام صيغة مؤهلة كليًا. توضح الشيفرة 21 كيفية استخدام صيغة مؤهلة كليًا.
اسم الملف: src/main.rs
fn main() { println!("A baby dog is called a {}", <Dog as Animal>::baby_name()); }
[الشيفرة 21: استعمال صيغة مؤهلة كليًا لتحديد أننا نريد استدعاء دالة baby_name
من السمة Animal
كما هو منفّذ في Dog
]
نقدم لرست تعليقًا توضيحيًا للنوع ضمن أقواس مثلثية angle brackets مما يشير إلى أننا نريد استدعاء تابع baby_name
من سمة Animal
كما هو منفّذ في Dog
بالقول إننا نريد معاملة النوع Dog
مثل النوع Animal
لاستدعاء الدالة هذه. ستطبع الشيفرة البرمجية الآن ما نريد:
$ cargo run Compiling traits-example v0.1.0 (file:///projects/traits-example) Finished dev [unoptimized + debuginfo] target(s) in 0.48s Running `target/debug/traits-example` A baby dog is called a puppy
تعرف الصيغة المؤهلة كليًا عمومًا على النحو التالي:
<Type as Trait>::function(receiver_if_method, next_arg, ...);
بالنسبة للدوال المرتبطة التي ليست توابع، لن يكون هناك receiver
، سيكون هناك فقط قائمة من الوسائط الأخرى. يمكنك استخدام صيغة مؤهلة كليًا في كل مكان تستدعي فيه دوالًا أو توابعًا، ومع ذلك يُسمح لك بحذف أي جزء من هذه الصيغة التي يمكن لرست اكتشافها من المعلومات الأخرى في البرنامج. ما عليك سوى استخدام هذه الصيغة المطولة أكثر في الحالات التي توجد فيها العديد من التنفيذات التي تستخدم الاسم ذاته وتحتاج رست إلى المساعدة في تحديد التنفيذ الذي تريد استدعاءه.
استخدام سمات خارقة supertrait لطلب وظيفة إحدى السمات ضمن سمة أخرى
في بعض الأحيان قد تكتب تعريف سمة يعتمد على سمة أخرى: لكي ينفّذ النوع السمة الأولى فأنت تريد أن تطلب هذا النوع لتنفيذ السمة الثانية أيضًا. يمكنك فعل ذلك حتى يتمكن تعريف السمة الخاص بك من الاستفادة من العناصر المرتبطة للسمة الثانية. تسمى السمة التي يعتمد عليها تعريف السمة الخاص بك بالسمة الخارقة لسمتك.
على سبيل المثال لنفترض أننا نريد إنشاء سمة OutlinePrint
باستخدام تابع outline_print
الذي سيطبع قيمة معينة منسقة بحيث تكون مؤطرة بعلامات نجمية. بالنظر إلى هيكل Point
الذي ينفّذ سمة المكتبة القياسية Display
لتعطي النتيجة (x, y)
، عندما نستدعي outline_print
على نسخة Point
التي تحتوي على 1 للقيمة x
و 3 للقيمة y
، يجب أن يطبع ما يلي:
********** * * * (1, 3) * * * **********
نريد استخدام وظيفة سمة Display
في تنفيذ طريقة outline_print
، لذلك نحتاج إلى تحديد أن سمة OutlinePrint
ستعمل فقط مع الأنواع التي تنفّذ أيضًا Display
وتوفر الوظائف التي تحتاجها OutlinePrint
. يمكننا فعل ذلك في تعريف السمة عن طريق تحديد OutlinePrint: Display
، وتشبه هذه التقنية إضافة سمة مرتبطة بالسمة. تُظهر الشيفرة 22 تنفيذ سمة OutlinePrint
.
اسم الملف: src/main.rs
use std::fmt; trait OutlinePrint: fmt::Display { fn outline_print(&self) { let output = self.to_string(); let len = output.len(); println!("{}", "*".repeat(len + 4)); println!("*{}*", " ".repeat(len + 2)); println!("* {} *", output); println!("*{}*", " ".repeat(len + 2)); println!("{}", "*".repeat(len + 4)); } }
[الشيفرة 22: تنفيذ سمة OutlinePrint
التي تتطلب الوظيفة من Display
]
بما أننا حددنا أن OutlinePrint
تتطلب سمة Display
، يمكننا استخدام دالة to_string
التي تُنفّذ تلقائيًا لأي نوع ينفّذ Display
. إذا حاولنا استخدام to_string
دون إضافة نقطتين وتحديد سمة Display
بعد اسم السمة، سنحصل على خطأ يقول بأنه لم يُعثر على تابع باسم to_string
للنوع Self&
في النطاق الحالي.
لنرى ما يحدث عندما نحاول تنفيذ OutlinePrint
على نوع لا ينفّذ Display
مثل هيكل Point
.
اسم الملف: src/main.rs
struct Point { x: i32, y: i32, } impl OutlinePrint for Point {}
نحصل على خطأ يقول أن Display
مطلوبة ولكن غير منفّذة:
$ cargo run Compiling traits-example v0.1.0 (file:///projects/traits-example) error[E0277]: `Point` doesn't implement `std::fmt::Display` --> src/main.rs:20:6 | 20 | impl OutlinePrint for Point {} | ^^^^^^^^^^^^ `Point` cannot be formatted with the default formatter | = help: the trait `std::fmt::Display` is not implemented for `Point` = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead note: required by a bound in `OutlinePrint` --> src/main.rs:3:21 | 3 | trait OutlinePrint: fmt::Display { | ^^^^^^^^^^^^ required by this bound in `OutlinePrint` For more information about this error, try `rustc --explain E0277`. error: could not compile `traits-example` due to previous error
لإصلاح هذا الأمر ننفّذ Display
على Point
ونلبي القيد الذي تتطلبه OutlinePrint
مثل:
اسم الملف: src/main.rs
use std::fmt; impl fmt::Display for Point { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "({}, {})", self.x, self.y) } }
سيُصرَّف تنفيذ سمة OutlinePrint
على Point
بعدها بنجاح، ويمكننا استدعاء outline_print
على نسخة Point
لعرضها ضمن مخطط من العلامات النجمية.
استخدام نمط النوع الجديد لتنفيذ سمات الخارجية على الأنواع الخارجية
ذكرنا سابقًا في الفصل السمات Traits في لغة رست في قسم "تطبيق السمة على نوع" القاعدة الوحيدة التي تنص على أنه لا يُسمح لنا إلا بتنفيذ سمة على نوع ما إذا كانت السمة أو النوع محليين بالنسبة للوحدة المصرفة الخاصة بنا، إلا أنه من الممكن التحايل على هذا القيد باستخدام نمط النوع الجديد Newtype pattern الذي يتضمن إنشاء نوع جديد في هيكل الصف (ناقشنا هياكل الصف سابقًا في قسم "استخدام هياكل الصفوف دون حقول مسماة لإنشاء أنواع مختلفة" من الفصل استخدام الهياكل structs لتنظيم البيانات في لغة رست)، إذ سيكون لهيكل الصف حقلًا واحدًا وسيكون هناك غلافًا رفيعًا حول النوع الذي نريد تنفيذ سمة له، ثم يكون نوع الغلاف محليًا بالنسبة للوحدة المصرفة الخاصة بنا ويمكننا تنفيذ السمة على الغلاف. النوع الجديد هو مصطلح ينشأ من لغة البرمجة هاسكل Haskell. لا يوجد تأثير سلبي على وقت التنفيذ لاستخدام هذا النمط ويُستبعد نوع الغلاف في وقت التصريف.
على سبيل المثال، لنفترض أننا نريد تنفيذ Display
على <Vec<T
التي تمنعنا القاعدة الوحيدة من فعل ذلك مباشرةً لأن سمة Display
ونوع <Vec<T
مُعرّفان خارج الوحدة المصرفة الخاصة بنا. يمكننا عمل هيكل Wrapper
يحتوي على نسخة من <Vec<T
ومن ثم تنفيذ Display
على Wrapper
واستخدام قيمة <Vec<T
كما هو موضح في الشيفرة 23.
اسم الملف: src/main.rs
use std::fmt; struct Wrapper(Vec<String>); impl fmt::Display for Wrapper { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "[{}]", self.0.join(", ")) } } fn main() { let w = Wrapper(vec![String::from("hello"), String::from("world")]); println!("w = {}", w); }
[الشيفرة 23: إنشاء نوع Wrapper
حول <Vec<String
لتنفيذ Display
]
تُستخدم self.0
من قِبل تنفيذ Display
للوصول إلى <Vec<T
الداخلي، لأن Wrapper
هيكل صف و <Vec<T
هو العنصر في الدليل 0 في الصف. يمكننا بعد ذلك استخدام وظيفة نوع Display
على Wrapper
.
الجانب السلبي لاستخدام هذه التقنية هو أن Wrapper
هو نوع جديد لذلك لا يحتوي على توابع القيمة التي يحملها. سيتوجب علينا تنفيذ جميع توابع <Vec<T
مباشرةً على Wrapper
، بحيث تفوض هذه التوابع إلى self.0
، ليسمح لنا بالتعامل مع Wrapper
تمامًا مثل <Vec<T
.
إذا أردنا أن يحتوي النوع الجديد على كل تابع يمتلكه النوع الداخلي، سيكون تنفيذ سمة Deref
(ناقشناها سابقًا في الفصل معاملة المؤشرات الذكية Smart Pointers مثل مراجع نمطية Regular References باستخدام سمة Deref في لغة رست) على Wrapper
لإرجاع النوع الداخلي حلًا مناسبًا. إذا كنا لا نريد أن يحتوي نوع Wrapper
على جميع التوابع من النوع الداخلي -على سبيل المثال لتقييد سلوك نوع Wrapper
- سيتعين علينا تنفيذ التوابع التي نريدها يدويًا فقط.
نموذج النمط الجديد هذا مفيد أيضًا حتى عندما لا تكون السمات متضمنة. لنغير تركيزنا الآن، ونلقي نظرةً على بعض الطرق المتقدمة للتفاعل مع نظام نوع رست.
ترجمة -وبتصرف- لقسم من الفصل Advanced Features من كتاب The Rust Programming Language.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.