الاستخدام المتقدم للسمات: الأنواع المرتبطة
استكشفوا الأنواع المرتبطة داخل السمات لتعريف مواضع لأنواع يجب أن تنفذها السمة، مما يتيح تجريدات أكثر مرونة.
الاستخدام المتقدم للسمات: الأنواع المرتبطة درس مجاني في Learn Rust Coding على CoddyKit. هذا هو الدرس 3 من أصل 3. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Learn Rust Coding، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Learn Rust Coding 3 دروس في المجموع.
ما الأنواع المرتبطة؟
مرحبًا بك في موضوع متقدم من سمات Rust: الأنواع المرتبطة! هذه ميزات قوية تضيف المرونة والوضوح إلى تعريفات السمات.
فكّر في النوع المرتبط على أنه نوع نائب يُعلن عنه داخل سمة. فبدلًا من أن تكون السمة نفسها عامة على نوع ما، يحدد منفّذ السمة النوع الفعلي لهذا النائب.
لماذا نستخدم الأنواع المرتبطة؟
تساعد الأنواع المرتبطة على جعل السمات أكثر أناقة وأسهل استخدامًا، لا سيما عندما تحدد السمة عدة أساليب تعمل جميعها على نوع مرتبط محدد.
- الوضوح: تجمع الأنواع المرتبطة الأنواع ذات الصلة مباشرة ضمن نطاق السمة.
- المرونة: تتيح لكل منفّذ للسمة تحديد أنواعه الخاصة لهذه الأنواع النائبة.
- تقليل التعليمات المتكررة: لا تحتاج إلى تكرار معاملات الأنواع العامة في جميع تواقيع الأساليب داخل السمة.
تعريف سمة بنوع مرتبط
لنلقِ نظرة على الصياغة الأساسية لتعريف سمة بنوع مرتبط. نستخدم الكلمة المفتاحية type داخل تعريف السمة.
هنا، تحتاج السمة Container إلى معرفة نوع Item الذي ستحتفظ به. ولا تحدد السمة نفسها هذا النوع، بل تحدد فقط وجود نوع من هذا القبيل.
trait Container {
type Item; // Associated type declaration
fn add(&mut self, item: Self::Item);
fn contains(&self, item: &Self::Item) -> bool;
}تنفيذ السمة
عند تنفيذ سمة ذات نوع مرتبط لنوع محدد (مثل struct)، يجب أن تذكر صراحةً ما سيكون عليه النوع الفعلي للنوع المرتبط.
في مثالنا، ينفذ MyVec السمة Container، ونعلن أن نوع Item فيه هو i32.
trait Container {
type Item;
fn add(&mut self, item: Self::Item);
fn contains(&self, item: &Self::Item) -> bool;
}
struct MyVec {
elements: Vec<i32>,
}
impl Container for MyVec {
type Item = i32; // Specify the concrete type for Item
fn add(&mut self, item: Self::Item) {
self.elements.push(item);
}
fn contains(&self, item: &Self::Item) -> bool {
self.elements.contains(item)
}
}
fn main() {
let mut my_vec = MyVec { elements: vec![] };
my_vec.add(10);
my_vec.add(20);
println!("Contains 10: {}", my_vec.contains(&10));
println!("Contains 30: {}", my_vec.contains(&30));
}الأنواع المرتبطة مقابل الأنواع العامة
هذا تمييز مهم! لو كانت Container عامة (مثل trait Container<T>)، لأمكنك تنفيذ Container<i32> for MyVec وContainer<String> for MyVec معًا.
أما مع النوع المرتبط، ففي حالة impl Container for MyVec معينة، لا يمكن أن يكون نوع Item إلا نوعًا محددًا واحدًا (مثل i32). ولا يمكنك تنفيذ Container لـ MyVec مرتين باستخدام نوعي Item مختلفين.
مثال من العالم الحقيقي: سمة `Iterator`
من أكثر الأمثلة شيوعًا ووضوحًا على الأنواع المرتبطة في مكتبة Rust القياسية سمة Iterator.
تحتوي سمة Iterator على نوع مرتبط يسمى Item، ويمثل نوع القيم التي سينتجها المكرّر. ويحدد كل منفّذ لـ Iterator بالضبط نوع Item الذي ينتجه.
// Simplified Iterator trait
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
struct Counter {
count: u32,
}
impl Iterator for Counter {
type Item = u32; // This iterator yields u32 values
fn next(&mut self) -> Option<Self::Item> {
if self.count < 5 {
self.count += 1;
Some(self.count)
} else {
None
}
}
}
fn main() {
let mut counter = Counter { count: 0 };
// Using the iterator directly
while let Some(num) = counter.next() {
println!("Current count: {}", num);
}
}تقييد الأنواع المرتبطة
كما هو الحال مع معاملات الأنواع العامة، يمكنك إضافة قيود سمات إلى الأنواع المرتبطة. ويضمن ذلك التزام النوع الفعلي الذي يختاره المنفّذ بسلوكيات أو قدرات معينة.
هنا، يعني Item: std::fmt::Debug أن النوع المرتبط يجب أن ينفذ سمة Debug، ما يتيح لنا طباعته.
trait PrintableContainer {
type Item: std::fmt::Debug; // Item must implement Debug
fn add(&mut self, item: Self::Item);
fn print_all(&self);
}
struct DebugVec {
elements: Vec<String>,
}
impl PrintableContainer for DebugVec {
type Item = String; // String implements Debug
fn add(&mut self, item: Self::Item) {
self.elements.push(item);
}
fn print_all(&self) {
for item in &self.elements {
println!("Item: {:?}", item); // Uses Debug formatting
}
}
}
fn main() {
let mut debug_vec = DebugVec { elements: vec![] };
debug_vec.add(String::from("Hello"));
debug_vec.add(String::from("World"));
debug_vec.print_all();
}الأنواع المرتبطة ذات القيم الافتراضية
لمزيد من المرونة، يمكن أن تمتلك الأنواع المرتبطة أنواعًا فعلية افتراضية. ويمكن للمنفّذ بعد ذلك اختيار استخدام النوع الافتراضي أو استبداله بنوع مختلف.
يفيد ذلك في السمات التي تمتلك سلوكًا افتراضيًا شائعًا، لكن قد تكون هناك حاجة إلى أنواع مخصصة في بعض الأحيان.
trait Processor {
type Input = String; // Default input type
type Output = String; // Default output type
fn process(&self, input: Self::Input) -> Self::Output;
}
struct SimpleProcessor;
impl Processor for SimpleProcessor {
// Here, we use the default Input and Output types (String)
fn process(&self, input: String) -> String {
format!("Processed: {}", input.to_uppercase())
}
}
struct CustomIntProcessor;
impl Processor for CustomIntProcessor {
type Input = i32; // Override default Input
type Output = i32; // Override default Output
fn process(&self, input: i32) -> i32 {
input * 2
}
}
fn main() {
let simple = SimpleProcessor;
println!("Simple processor: {}", simple.process(String::from("hello rust")));
let custom = CustomIntProcessor;
println!("Custom int processor: {}", custom.process(10));
}متى تختار الأنواع المرتبطة؟
متى ينبغي أن تختار نوعًا مرتبطًا بدلًا من معامل نوع عام في السمة نفسها؟
- عندما يعمل مفهوم السمة على نوع مرتبط محدد واحد لكل تنفيذ (مثلًا، ينتج
Iteratorدائمًا نوعًا واحدًا منItem). - لتجنب تكرار المعاملات العامة في كل توقيع أسلوب، ما يؤدي إلى تعريفات سمات أكثر وضوحًا.
- عندما تريد تعريف نوع ضمن نطاق السمة، بدلًا من جعل السمة نفسها عامة.
تحقق من فهمك
لنختبر سريعًا مدى استيعابك للأنواع المرتبطة.
مراجعة: الأنواع المرتبطة
أحسنتم! في هذا الدرس، استكشفتم الأنواع المرتبطة في سمات Rust. تعلّمتم ما يلي:
- الأنواع المرتبطة هي أنواع نائبة تُعرَّف داخل سمة.
- يحدّد منفّذو السمة النوع الفعلي لهذه الأنواع النائبة.
- توفّر الوضوح والمرونة من خلال تجميع الأنواع المرتبطة مباشرةً داخل السمة.
- تختلف عن معاملات السمات العامة، إذ تضمن نوعًا فعليًا واحدًا لتنفيذ سمة معيّن.
- توضّح أمثلة مثل السمة
Iteratorاستخدامها العملي.
يساعدكم إتقان الأنواع المرتبطة على كتابة شيفرة Rust أكثر متانة واتباعًا للأساليب المألوفة، خصوصًا عند تصميم تجريدات معقدة قائمة على السمات!
الأسئلة الشائعة
هل درس «الاستخدام المتقدم للسمات: الأنواع المرتبطة» مجاني؟
نعم — نص درس «الاستخدام المتقدم للسمات: الأنواع المرتبطة» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Learn Rust Coding، انتقل إلى CoddyKit PRO. تتضمن دورة Learn Rust Coding 3 دروس في المجموع.
ماذا ستتعلم في «الاستخدام المتقدم للسمات: الأنواع المرتبطة»؟
استكشفوا الأنواع المرتبطة داخل السمات لتعريف مواضع لأنواع يجب أن تنفذها السمة، مما يتيح تجريدات أكثر مرونة. تتمرن على Learn Rust Coding مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ Learn Rust Coding؟
لا تُشترط خبرة سابقة. Learn Rust Coding على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 3 من أصل 3.
كم من الوقت يستغرق درس «الاستخدام المتقدم للسمات: الأنواع المرتبطة»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس Learn Rust Coding هذا؟
نعم. كل درس في Learn Rust Coding يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- كتابة شيفرة عامة في Rust
- تعريف السمات وتنفيذها
- الاستخدام المتقدم للسمات: الأنواع المرتبطة