تعريف السمات وتنفيذها
أتقنوا السمات لتعريف السلوك المشترك بين الأنواع المختلفة، على نحو يشبه الواجهات في اللغات الأخرى.
تعريف السمات وتنفيذها درس مجاني في Learn Rust Coding على CoddyKit. هذا هو الدرس 2 من أصل 3. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Learn Rust Coding، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Learn Rust Coding 3 دروس في المجموع.
ما السمات في Rust؟
في Rust، تُعد السمات طريقة قوية لتعريف سلوك مشترك بين أنواع مختلفة. فكّر فيها على أنها واجهات في اللغات الأخرى.
تُخبر السمة Rust بأن نوعًا معينًا يمتلك وظائف محددة. وإذا نفّذ نوعٌ سمةً ما، فهذا يعني أنه يوفر الأساليب التي تحددها تلك السمة.
- تتيح السمات تعدد الأشكال: أي التعامل مع أنواع مختلفة بطريقة موحّدة.
- وهي عنصر أساسي في نظام الأنواع والتجريدات الآمنة في Rust.
التصريح عن سمتك الأولى
تعرّف السمة باستخدام الكلمة المفتاحية trait. وداخلها، تسرد تواقيع الأساليب (الأسماء والمعاملات وأنواع الإرجاع) التي يجب على أي نوع ينفذها توفيرها.
لنعرّف سمة بسيطة باسم Summary للعناصر التي يمكن تلخيصها.
trait Summary {
fn summarize(&self) -> String;
}
fn main() {
// Traits are definitions, not directly executable.
// We will implement and use them in later scenes!
println!("Trait 'Summary' defined.");
}تنفيذ السمات للأنواع
لجعل نوع ما يستخدم سمة، تستعمل صياغة impl Trait for Type. ثم توفر التنفيذ الفعلي لكل أسلوب مُعرّف في السمة.
هنا، ننفذ Summary للهيكل NewsArticle.
trait Summary {
fn summarize(&self) -> String;
}
struct NewsArticle {
headline: String,
location: String,
author: String,
content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
fn main() {
let article = NewsArticle {
headline: String::from("Penguins win Stanley Cup!"),
location: String::from("Pittsburgh, PA"),
author: String::from("Iceburgh"),
content: String::from("The Pittsburgh Penguins have won..."),
};
println!("New article summary: {}", article.summarize());
}استخدام الدوال ذات قيود السمات
بعد أن ينفذ نوع ما سمةً، يمكنك كتابة دوال تقبل أي نوع ينفذ تلك السمة. يتيح ذلك كتابة تعليمات برمجية مرنة وعامة.
يمكن للدالة notify استقبال أي نوع ينفذ Summary.
trait Summary {
fn summarize(&self) -> String;
}
struct NewsArticle {
headline: String,
location: String,
author: String,
content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
struct Tweet {
username: String,
content: String,
reply: bool,
retweet: bool,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
fn notify(item: &impl Summary) { // impl Summary is syntax sugar for a trait bound
println!("Breaking news! {}", item.summarize());
}
fn main() {
let tweet = Tweet {
username: String::from("horse_ebooks"),
content: String::from("of course, as you probably already know, people"),
reply: false,
retweet: false,
};
let article = NewsArticle {
headline: String::from("Rust is awesome!"),
location: String::from("Internet"),
author: String::from("Rustacean"),
content: String::from("Rust's type system is amazing."),
};
notify(&tweet);
notify(&article);
}السمات ذات السلوك الافتراضي
يمكن للسمات أيضًا توفير تنفيذات افتراضية لأساليبها. وهذا يعني أن الأنواع لا يتعين عليها تنفيذ كل أسلوب إذا كان له تنفيذ افتراضي.
ويمكنها اختيار استخدام التنفيذ الافتراضي أو استبداله بمنطقها الخاص.
trait Summary {
fn summarize_author(&self) -> String; // New required method
fn summarize(&self) -> String { // Default implementation
format!("(Read more from {})", self.summarize_author())
}
}
struct Tweet {
username: String,
content: String,
reply: bool,
retweet: bool,
}
impl Summary for Tweet {
fn summarize_author(&self) -> String { // Must implement required method
format!("@{}", self.username)
}
// We are using the default summarize() method here!
}
fn main() {
let tweet = Tweet {
username: String::from("dog_lover"),
content: String::from("My dog is the best!"),
reply: false,
retweet: false,
};
println!("Tweet summary: {}", tweet.summarize());
}الدوال العامة مع السمات
عند كتابة دوال أو هياكل عامة، يمكنك استخدام قيود السمات لتحديد أن معامل النوع العام T يجب أن ينفذ سمة معينة.
ويضمن ذلك توفر أساليب تلك السمة لـ T.
trait Displayable {
fn display(&self);
}
struct Point<T> {
x: T,
y: T,
}
// Implement Displayable for Point<i32>
impl Displayable for Point<i32> {
fn display(&self) {
println!("Point: ({}, {})", self.x, self.y);
}
}
// Generic function that works for any type T that implements Displayable
fn print_item<T: Displayable>(item: T) {
item.display();
}
fn main() {
let p = Point { x: 10, y: 20 };
print_item(p);
// This function only works for types that implement Displayable
// let s = String::from("Hello");
// print_item(s); // This would cause a compile-time error
}دمج قيود السمات المتعددة
قد يحتاج النوع العام أحيانًا إلى تنفيذ أكثر من سمة واحدة. ويمكنك تحديد قيود سمات متعددة باستخدام العامل +.
وفي السيناريوهات الأكثر تعقيدًا، ولا سيما عند وجود معاملات عامة كثيرة، قد تحسّن جملة where قابلية القراءة.
use std::fmt::Debug;
trait Printable {
fn print_info(&self);
}
struct Book {
title: String,
pages: u32,
}
impl Printable for Book {
fn print_info(&self) {
println!("Book: '{}' ({} pages)", self.title, self.pages);
}
}
impl Debug for Book { // Book also implements Debug trait
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Book {{ title: {}, pages: {} }}", self.title, self.pages)
}
}
// A function that requires its argument to be both Printable and Debug
fn process_item<T: Printable + Debug>(item: T) {
item.print_info();
println!("Debug info: {:?}", item);
}
fn main() {
let my_book = Book {
title: String::from("The Rust Book"),
pages: 600,
};
process_item(my_book);
}إرجاع الأنواع باستخدام `impl Trait`
لا تقتصر صياغة impl Trait على معاملات الدوال؛ إذ يمكن استخدامها أيضًا في مواضع الإرجاع. ويفيد ذلك عندما تريد إرجاع نوع ينفذ سمة معينة، لكنك لا تريد كشف نوعه الفعلي الدقيق.
فهي تبسّط تواقيع الدوال وتحافظ على مرونة واجهة API الخاصة بك.
trait Greeter {
fn greet(&self) -> String;
}
struct FriendlyGreeter;
impl Greeter for FriendlyGreeter {
fn greet(&self) -> String {
String::from("Hello there!")
}
}
struct FormalGreeter;
impl Greeter for FormalGreeter {
fn greet(&self) -> String {
String::from("Greetings and salutations.")
}
}
// This function returns *some* type that implements Greeter
// The caller doesn't need to know if it's FriendlyGreeter or FormalGreeter
fn get_greeter(formal: bool) -> impl Greeter {
if formal {
FormalGreeter
} else {
FriendlyGreeter
}
}
fn main() {
let casual_greet = get_greeter(false);
println!("{}", casual_greet.greet());
let formal_greet = get_greeter(true);
println!("{}", formal_greet.greet());
}البناء على السمات العليا
قد تعتمد إحدى السمات منطقيًا على سمة أخرى. وتتيح لك Rust تعريف السمات العليا، أي إن السمة يمكن أن تشترط على أي نوع ينفذها أن ينفذ أيضًا سمة أخرى محددة.
يتم ذلك باستخدام صياغة trait SubTrait: SuperTrait. ويشبه هذا القول: «إذا كنت قادرًا على تنفيذ X، فيجب أن تكون قادرًا أيضًا على تنفيذ Y».
trait Printable {
fn print_content(&self);
}
// Debug is a supertrait of PrintableDebug
// Any type implementing PrintableDebug must also implement Debug
trait PrintableDebug: Printable + std::fmt::Debug {
fn print_debug_and_content(&self) {
self.print_content();
println!("Debug info: {:?}", self);
}
}
struct Item {
id: u32,
name: String,
}
impl Printable for Item {
fn print_content(&self) {
println!("Item ID: {}, Name: {}", self.id, self.name);
}
}
impl std::fmt::Debug for Item {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Item {{ id: {}, name: {} }}", self.id, self.name)
}
}
// Now we can implement PrintableDebug because Item implements both Printable and Debug
impl PrintableDebug for Item {}
fn main() {
let my_item = Item {
id: 101,
name: String::from("Widget A"),
};
my_item.print_debug_and_content();
}اختبر معلوماتك عن السمات
تأمل مقطع تعليمات Rust البرمجية التالي. أي العبارات التالية حول السمات وقيود السمات صحيحة؟
السمات: أدواتك للسلوك
لقد استكشفنا نظام السمات القوي في Rust!
- تعرّف السمات مجموعة من الأساليب التي يمكن لنوع ما تنفيذها، وتعمل مثل الواجهات.
- تنفذ السمات لأنواع محددة باستخدام
impl Trait for Type. - تتيح قيود السمات (
T: Trait) للدوال والهياكل العامة العمل مع أي نوع ينفذ سمة مطلوبة. - يمكن أن تمتلك السمات تنفيذات افتراضية، كما يمكنك تحديد قيود سمات متعددة (
T: Trait1 + Trait2) أو استخدام السمات العليا. - يمكن أيضًا استخدام
impl Traitفي مواضع الإرجاع لإخفاء الأنواع الفعلية.
تُعد السمات أساسية لكتابة تعليمات Rust برمجية مرنة وقابلة لإعادة الاستخدام وآمنة!
الأسئلة الشائعة
هل درس «تعريف السمات وتنفيذها» مجاني؟
نعم — نص درس «تعريف السمات وتنفيذها» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Learn Rust Coding، انتقل إلى CoddyKit PRO. تتضمن دورة Learn Rust Coding 3 دروس في المجموع.
ماذا ستتعلم في «تعريف السمات وتنفيذها»؟
أتقنوا السمات لتعريف السلوك المشترك بين الأنواع المختلفة، على نحو يشبه الواجهات في اللغات الأخرى. تتمرن على Learn Rust Coding مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ Learn Rust Coding؟
لا تُشترط خبرة سابقة. Learn Rust Coding على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 3.
كم من الوقت يستغرق درس «تعريف السمات وتنفيذها»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس Learn Rust Coding هذا؟
نعم. كل درس في Learn Rust Coding يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- كتابة شيفرة عامة في Rust
- تعريف السمات وتنفيذها
- الاستخدام المتقدم للسمات: الأنواع المرتبطة